Skip to content

Repository files navigation

StreamFinder

Overview

StreamFinder is a single-module Android app, built with Jetpack Compose and Material 3, that browses popular movies and TV shows via the TMDB (The Movie Database) API. It supports searching, viewing details with trailers, and maintaining an offline list of favorites backed by a local database.

Features

  • Home — browses popular movies and popular TV shows in horizontally scrollable rows.
  • Search — full-text search across movies and TV shows (TMDB /search/multi), debounced to avoid firing a request on every keystroke.
  • Detail — backdrop image, title, rating, release year, runtime/season count, genres, overview, trailer playback (opens YouTube), and share action.
  • Favorites — persisted locally with Room, toggle from the detail screen, view as a grid, survives app restarts without any network calls.
  • Navigation — Search and Favorites open as modal dialogs over Home, Detail is a full destination reachable from Home, Search, or Favorites.
  • Theming — Material 3 with dynamic color on Android 12+, a static purple/pink palette on older versions, and RTL layout support.

Architecture

MVVM + Clean Architecture, split into three layers:

  • ui/ — Compose screens, ViewModels, and UI state. Each screen exposes state via StateFlow<UiState<T>> and reacts to it with when on a sealed UiState (Loading / Success / Error).
  • domain/ — Plain Kotlin models (MediaItem, MediaDetail, MediaType) and repository interfaces. No Android or networking dependencies.
  • data/ — Repository implementations that mediate between the remote API (Retrofit) and local storage (Room), mapping DTOs to domain models.
com.lev.streamfinder
├── data
│   ├── local
│   │   ├── AppDatabase.kt
│   │   ├── FavoritesDao.kt
│   │   └── FavoriteEntity.kt
│   ├── remote
│   │   ├── TmdbApiService.kt
│   │   ├── AuthInterceptor.kt
│   │   └── dto/
│   └── repository/
│       ├── MovieRepository.kt / MovieRepositoryImpl.kt
│       └── TvRepository.kt / TvRepositoryImpl.kt
├── domain
│   ├── model/          (MediaItem, MediaDetail, MediaType)
│   └── usecase/
├── ui
│   ├── home/
│   ├── search/
│   ├── favorites/
│   ├── detail/
│   ├── navigation/
│   └── theme/
└── di/                  (Hilt modules: Network, Database, Repository, Coil)

Dependency injection (Hilt) wires repositories into ViewModels and provides singletons for the Retrofit client, Room database, and Coil ImageLoader, keeping each layer unaware of how its dependencies are constructed.

Tech Stack

Library Purpose
Jetpack Compose + Material 3 Declarative UI, no XML layouts
Hilt Dependency injection across ViewModels, repositories, and data sources
Retrofit + Gson Type-safe HTTP client for the TMDB REST API
OkHttp Underlying HTTP client, used for the API key auth interceptor and image cache-control interceptor
Coil Async image loading with disk/memory caching for posters and backdrops
Room Local persistence for favorites, exposed as Flow for reactive UI updates
Navigation Compose Screen and dialog navigation (NavHost, dialog() destinations)
Media3 (ExoPlayer) Media playback support
Kotlin Coroutines / Flow Async data loading, debouncing, and reactive state
MockK Mocking in unit tests
Turbine Testing Flow/StateFlow emissions
Robolectric Running Room DAO tests against a real in-memory SQLite database under JVM unit tests

Setup Instructions

  1. Get a TMDB API key:
  2. Copy the example properties file:
    copy local.properties.example local.properties
  3. Open local.properties and set:
    sdk.dir=/path/to/your/Android/Sdk
    TMDB_API_KEY=your_tmdb_api_key_here
    
  4. local.properties is git-ignored, the key is injected into BuildConfig.TMDB_API_KEY at build time and attached to every TMDB request by AuthInterceptor.
  5. Build the debug APK:
    .\gradlew assembleDebug

Testing

Run the unit test suite:

.\gradlew test

Run a single test class:

.\gradlew test --tests "com.lev.streamfinder.ui.search.SearchViewModelTest"

Coverage:

  • SearchViewModelTest — verifies the 300ms search debounce: no request fires before the window elapses, exactly one request fires once it does, and rapid keystrokes collapse into a single search for the final query.
  • FavoritesDaoTest — exercises insert, deleteById, isFavorite, and getAllFavorites (including sort order) against a real in-memory Room database via Robolectric.
  • MovieRepositoryImplTest — verifies DTO-to-domain mapping (including dropping unrecognized media_type values from multi-search results) and that thrown exceptions are wrapped as Result.failure rather than propagating.

Tests live under app/src/test/java/com/lev/streamfinder/, mirroring the main source package structure.

Screenshots

Icon Home Search Detail Favorites
Icon Home Search Detail Favorites

About

Modern Android app built with Compose, MVVM, Hilt, Room and TMDB API

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages