Unggulan

Penjelasan Proyek - MovFlix

Dokumentasi Lengkap Proyek MovFlix — Aplikasi Katalog Film Android

Nama: Christoforus Indra Bagus Pratama

NRP: 5025231124

Mata Kuliah: Pemrograman Perangkat Bergerak

Tanggal: 16 Juni 2026

Proyek: MovFlix — Movie Catalogue App


Bagian 1: Pemahaman Konsep & Bahasa Pemrograman

1. Bahasa Pemrograman (Kotlin)

Kotlin adalah bahasa pemrograman modern yang bersifat statically typed, dikembangkan oleh JetBrains, dan secara resmi didukung oleh Google sebagai bahasa utama (first-class language) untuk pengembangan Android sejak tahun 2019. Kotlin berjalan di atas Java Virtual Machine (JVM), sehingga sepenuhnya interoperable dengan kode Java yang sudah ada. Seluruh kode pada proyek MovFlix ditulis menggunakan Kotlin, sebagaimana terlihat pada semua file berekstensi .kt di dalam package com.example.moviecatalogue.

Berikut adalah fitur-fitur unggulan Kotlin yang diterapkan secara nyata dalam proyek MovFlix:

  • Null Safety: Kotlin memiliki sistem tipe yang membedakan antara referensi yang boleh bernilai null (nullable, ditandai dengan ?) dan yang tidak. Pada proyek ini, contoh penerapannya terlihat pada data class Movie di mana properti posterPath, backdropPath, dan trailerKey dideklarasikan sebagai String? (nullable) karena tidak semua film memiliki data gambar atau trailer. Sementara properti title dan id bersifat non-null karena pasti tersedia. Ini mencegah NullPointerException pada saat runtime.

  • Conciseness (Ringkas): Kotlin memungkinkan penulisan kode yang jauh lebih ringkas dibanding Java. Contoh nyatanya adalah penggunaan data class untuk model domain. Cukup satu baris deklarasi, Kotlin secara otomatis menghasilkan method equals(), hashCode(), toString(), dan copy().

  • Coroutines: Kotlin menyediakan coroutines untuk pemrograman asinkron yang ringan dan mudah dibaca. Pada proyek MovFlix, hampir semua operasi jaringan (API call) dan database dikerjakan menggunakan suspend function di dalam viewModelScope.launch { }. Ini menghindari pemblokiran main thread (UI thread) tanpa perlu mengelola thread secara manual.

  • Extension Functions: Kotlin memungkinkan penambahan fungsi baru pada kelas yang sudah ada tanpa memodifikasi kode sumbernya. Pada file MovieEntity.kt, terdapat extension function Movie.toEntity(userId) yang mengkonversi domain model ke entity database.

  • Higher-Order Functions & Lambda: Kotlin mendukung fungsi sebagai first-class citizen. Pada proyek ini, penggunaan .map { it.toDomain() }, .filter { }, dan .fold(onSuccess, onFailure) digunakan secara intensif untuk transformasi data secara elegan.

Contoh penerapan data class dan null safety pada proyek:

// domain/Movie.kt
data class Movie(
    val id: Int,                         // Non-null: pasti tersedia
    val title: String,                   // Non-null
    val overview: String,
    val posterPath: String?,             // Nullable: tidak semua film punya poster
    val backdropPath: String?,           // Nullable
    val releaseDate: String,
    val voteAverage: Double,
    val runtime: Int? = null,            // Nullable dengan default null
    val genres: List<String> = emptyList(),
    val trailerKey: String? = null,      // Nullable: trailer mungkin tidak tersedia
)

Mengapa Kotlin lebih direkomendasikan dibanding Java? Kotlin menawarkan kode yang lebih ringkas (rata-rata 40% lebih sedikit baris kode), null safety bawaan yang mencegah crash, coroutines native untuk operasi asinkron, serta kompatibilitas penuh dengan ekosistem Java yang sudah matang. Google sendiri menyatakan bahwa lebih dari 70% aplikasi teratas di Play Store menggunakan Kotlin, dan seluruh library Jetpack modern (Jetpack Compose, Room, Navigation) dirancang dengan Kotlin sebagai prioritas utama.


2. Arsitektur Aplikasi (MVVM)

MVVM (Model-View-ViewModel) adalah pola arsitektur perangkat lunak yang memisahkan logika bisnis dari antarmuka pengguna dengan membagi aplikasi menjadi tiga komponen utama. Arsitektur ini direkomendasikan secara resmi oleh Google untuk pengembangan aplikasi Android modern, dan diterapkan secara penuh pada proyek MovFlix.

Tiga komponen utama MVVM beserta perannya:

  • Model: Merepresentasikan data dan logika bisnis aplikasi. Pada MovFlix, Model mencakup domain model (Movie.kt, UserSession.kt, Genre.kt), database entity (MovieEntity.kt, UserEntity.kt), Data Transfer Object/DTO (Dtos.kt), serta Repository (MovieRepositoryImpl.kt, AuthRepositoryImpl.kt) yang menjadi Single Source of Truth untuk akses data.

  • View: Merupakan lapisan antarmuka pengguna yang bertanggung jawab menampilkan data dan menerima interaksi pengguna. Pada MovFlix, seluruh View dibangun menggunakan Jetpack Compose dalam bentuk fungsi @Composable. Contohnya: HomeScreen.kt, SearchScreen.kt, DetailScreen.kt, LoginScreen.kt, dan ProfileScreen.kt.

  • ViewModel: Berfungsi sebagai jembatan antara Model dan View. ViewModel menyimpan dan mengelola state UI, memproses logika presentasi, dan bertahan terhadap perubahan konfigurasi (seperti rotasi layar). Pada MovFlix, terdapat empat ViewModel: HomeViewModel.kt, SearchViewModel.kt, DetailViewModel.kt, dan AuthViewModel.kt.

Alur data dalam MVVM pada proyek MovFlix:

Tahap Komponen Aksi
1View (Compose Screen)User melakukan aksi (membuka app, klik film, mengetik pencarian).
2ViewModelView memanggil method ViewModel (misal load() atau onQueryChange()).
3ViewModel → RepositoryViewModel memanggil Repository dalam viewModelScope.launch { } untuk fetch data.
4Repository → API / RoomRepository menentukan sumber data: API (Retrofit) untuk data remote, Room untuk data lokal.
5Repository → ViewModelRepository mengembalikan Result<T> ke ViewModel (success atau failure).
6ViewModel → StateFlowViewModel mengupdate StateFlow dengan data baru atau pesan error.
7View (Recomposition)Compose secara otomatis melakukan recomposition saat nilai StateFlow berubah, memperbarui UI.

Keuntungan MVVM dibandingkan MVC atau MVP:

  • Separation of Concerns: Setiap komponen memiliki tanggung jawab yang jelas dan terpisah, sehingga kode lebih mudah dibaca dan dipelihara.

  • Lifecycle-Aware: ViewModel secara otomatis bertahan saat Activity/Fragment dihancurkan dan dibuat ulang (misal saat rotasi layar), sehingga data tidak hilang.

  • Reactive Data Binding: Penggunaan StateFlow memungkinkan View secara reaktif memperbarui UI saat data berubah, tanpa perlu pemanggilan manual notifyDataSetChanged().

  • Testability: ViewModel dan Repository dapat di-unit test secara independen tanpa bergantung pada komponen Android.

Intinya, MVVM menjadi standar arsitektur terbaik untuk aplikasi Android modern karena ia memisahkan logika bisnis dari UI serta didukung oleh komponen yang lifecycle-aware dan reaktif, sehingga data aplikasi kamu aman dari risiko hilang saat layar berotasi, UI otomatis sinkron secara real-time, dan kodenya jauh lebih mudah dirawat maupun diuji.


3. Penyimpanan Data Lokal (Room Database)

Room Database adalah library persistence dari Android Jetpack yang menyediakan lapisan abstraksi di atas SQLite. Room memudahkan akses database lokal dengan menyediakan compile-time verification terhadap query SQL, integrasi native dengan Kotlin Coroutines dan Flow, serta pola arsitektur yang bersih melalui DAO (Data Access Object).

Room memiliki tiga komponen utama yang seluruhnya diterapkan dalam proyek MovFlix:

  • Entity: Kelas yang merepresentasikan tabel dalam database. Pada MovFlix, terdapat dua entity: MovieEntity (tabel watchlist_movies) untuk menyimpan daftar film yang ditandai favorit, dan UserEntity (tabel users) untuk menyimpan data akun pengguna yang terdaftar. Setiap entity dianotasi dengan @Entity dan mendefinisikan primary key serta kolom-kolomnya.

  • DAO (Data Access Object): Interface yang mendefinisikan operasi database menggunakan anotasi SQL. Pada MovFlix, terdapat MovieDao untuk operasi CRUD watchlist dan UserDao untuk operasi akun pengguna. Setiap method DAO dianotasi dengan @Insert, @Query, atau @Delete.

  • RoomDatabase: Kelas abstrak yang menjadi titik akses utama ke database dan mendefinisikan daftar entity serta versi database. Pada MovFlix, kelas MovieDatabase mengimplementasikan pola Singleton untuk memastikan hanya ada satu instance database sepanjang masa hidup aplikasi.

// data/local/MovieEntity.kt — Entity untuk tabel watchlist
@Entity(tableName = "watchlist_movies", primaryKeys = ["id", "userId"])
data class MovieEntity(
    val id: Int,
    val userId: Int,         // Watchlist terpisah per akun!
    val title: String,
    val overview: String,
    val posterPath: String?,
    val releaseDate: String,
    val voteAverage: Double,
    val addedAt: Long = System.currentTimeMillis()
)

// data/local/UserEntity.kt — Entity untuk tabel akun pengguna
@Entity(tableName = "users", indices = [Index(value = ["email"], unique = true)])
data class UserEntity(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    val name: String,
    val email: String,
    val passwordHash: String,  // Password di-hash, bukan plain text!
    val salt: String,
)

Mengapa Room lebih disukai dibandingkan SQLite murni?

  • Compile-Time Verification: Query SQL divalidasi saat kompilasi, bukan saat runtime. Jika ada kesalahan penulisan query, error langsung terdeteksi di Android Studio.

  • Boilerplate Minimal: Room secara otomatis menghasilkan implementasi DAO dan mapping antara objek Kotlin dan baris tabel database.

  • Integrasi Coroutines & Flow: DAO bisa langsung mengembalikan Flow<List<T>> yang secara otomatis memperbarui UI saat data di database berubah. Pada MovFlix, method getAllWatchlistMovies() mengembalikan Flow sehingga watchlist selalu up-to-date.

  • Migration Support: Room mendukung migrasi versi database yang terstruktur. MovFlix menggunakan fallbackToDestructiveMigration() untuk fase pengembangan.


4. Integrasi Data Eksternal (REST API)

REST API (Representational State Transfer Application Programming Interface) adalah standar arsitektur komunikasi berbasis HTTP yang memungkinkan aplikasi client (dalam hal ini aplikasi Android MovFlix) berinteraksi dengan server eksternal untuk mengambil atau mengirim data. REST API menggunakan metode HTTP standar seperti GET, POST, PUT, dan DELETE, serta umumnya mengirimkan data dalam format JSON (JavaScript Object Notation).

Cara kerja REST API dalam aplikasi mobile:

  1. Aplikasi mobile (client) mengirimkan HTTP Request ke URL endpoint server tertentu, misalnya: GET https://api.themoviedb.org/3/trending/movie/week.

  2. Server memproses request dan mengembalikan HTTP Response berisi data dalam format JSON.

  3. Aplikasi mem-parse (deserialisasi) JSON tersebut menjadi objek Kotlin (data class) untuk ditampilkan di UI.

Pada proyek MovFlix, REST API yang digunakan adalah TMDB (The Movie Database) API, dan library yang dipakai untuk komunikasi HTTP adalah:

  • Retrofit 2: Library HTTP client type-safe buatan Square, yang memungkinkan pendefinisian endpoint API sebagai interface Kotlin dengan anotasi @GET, @POST, dll. Retrofit secara otomatis mengkonversi response JSON ke objek Kotlin menggunakan converter.

  • OkHttp: Library HTTP client low-level yang digunakan Retrofit sebagai transport layer. OkHttp juga menyediakan fitur interceptor untuk menyisipkan header autentikasi (Bearer Token) pada setiap request secara otomatis, serta logging interceptor untuk debugging.

  • Gson: Library buatan Google untuk serialisasi/deserialisasi JSON ke/dari objek Kotlin/Java. Diintegrasikan dengan Retrofit melalui GsonConverterFactory.

// Contoh alur request pada MovFlix:
// 1. ApiService mendefinisikan endpoint:
@GET("trending/movie/week")
suspend fun getTrendingMovies(): MovieResponse

// 2. Retrofit + OkHttp mengirim HTTP GET request ke:
//    https://api.themoviedb.org/3/trending/movie/week
//    dengan header: Authorization: Bearer {TMDB_TOKEN}

// 3. Gson mengkonversi JSON response ke MovieResponse:
data class MovieResponse(
    @SerializedName("results") val results: List<MovieDTO>
)

// 4. Repository memetakan DTO ke domain model:
results.map { it.toDomain() }

Bagian 2: Implementasi Arsitektur & Struktur Kode pada Proyek

5. Penerapan MVVM pada Proyek

Arsitektur MVVM diimplementasikan secara terstruktur dan konsisten pada seluruh fitur aplikasi MovFlix. Berikut adalah pemetaan setiap komponen MVVM ke file dan kelas spesifik dalam proyek:

Komponen MVVM File / Kelas Tanggung Jawab
Model domain/Movie.kt Domain model utama: data class Movie, Genre, MovieVideo, MovieDetail.
domain/UserSession.kt Data class sesi pengguna yang sedang login (userId, name, email, isGuest).
domain/MovieRepository.kt Interface repository yang mendefinisikan kontrak operasi data film.
domain/AuthRepository.kt Interface repository untuk operasi autentikasi (register, login, logout).
data/repository/MovieRepositoryImpl.kt Implementasi konkrit MovieRepository: menjembatani API dan Room.
data/repository/AuthRepositoryImpl.kt Implementasi konkrit AuthRepository: validasi dan hash password.
data/remote/Dtos.kt Data Transfer Objects: MovieDTO, MovieDetailDTO, GenreDTO, VideoDTO dengan mapping ke domain.
View ui/screens/home/HomeScreen.kt Halaman utama: hero slider trending, baris kategori Now Playing, Popular, Top Rated.
ui/screens/search/SearchScreen.kt Halaman pencarian: search bar debounced, filter genre chips, grid hasil.
ui/screens/detail/DetailScreen.kt Halaman detail film: backdrop, sinopsis, genre chips, trailer YouTube.
ui/screens/auth/LoginScreen.kt Halaman login: form email & password, tombol Guest, link ke register.
ui/screens/auth/RegisterScreen.kt Halaman registrasi: form nama, email, password.
ui/screens/profile/ProfileScreen.kt Halaman profil: avatar, nama, email, grid watchlist, tombol logout.
ViewModel ui/screens/home/HomeViewModel.kt Mengelola state 4 kategori film (trending, now playing, popular, top rated) via StateFlow.
ui/screens/search/SearchViewModel.kt Mengelola state pencarian: query debounced 400ms, filter genre, hasil pencarian.
ui/screens/detail/DetailViewModel.kt Mengelola state detail film: Loading/Success/Error, status watchlist, toggle watchlist.
ui/screens/auth/AuthViewModel.kt Mengelola state login & register: error messages, pemanggilan repository autentikasi.

Contoh alur MVVM pada fitur Home: ketika HomeScreen pertama kali ditampilkan, ia membaca state dari HomeViewModel. Pada saat init, HomeViewModel langsung memanggil empat method repository secara paralel (getTrending(), getNowPlaying(), getPopular(), getTopRated()) dalam viewModelScope.launch { }. Setiap hasil disimpan dalam MutableStateFlow yang di-observe oleh HomeScreen melalui collectAsState(). Saat data berhasil dimuat, Compose melakukan recomposition otomatis dan menampilkan daftar film.


6. Penerapan Layering (Pemisahan Lapisan)

Layering atau pemisahan lapisan adalah prinsip arsitektur di mana kode diorganisasi ke dalam lapisan-lapisan yang memiliki tanggung jawab spesifik. Google merekomendasikan tiga lapisan utama untuk aplikasi Android modern: UI Layer, Domain Layer, dan Data Layer. Proyek MovFlix menerapkan ketiga lapisan ini secara eksplisit melalui pembagian folder/package.

Layer Package / Folder Fungsi File-File di Dalamnya
UI Layer ui/ Menampilkan data ke pengguna dan menerima input. Berisi Composable screens, components, navigation, theme, dan ViewModel. screens/ (Home, Search, Detail, Auth, Profile), components/ (MovieCard, MovieSlider, YouTubeTrailerPlayer), navigation/, theme/
Domain Layer domain/ Mendefinisikan model bisnis murni dan kontrak repository. Layer ini tidak bergantung pada framework Android maupun library pihak ketiga. Movie.kt, MovieRepository.kt, AuthRepository.kt, UserSession.kt
Data Layer data/ Mengimplementasikan akses data dari berbagai sumber (API remote dan database lokal). Berisi implementasi repository, entity Room, DTO, API service, serta logic autentikasi. remote/ (ApiService, Dtos), local/ (MovieDao, UserDao, Entities, Database), repository/ (MovieRepositoryImpl, AuthRepositoryImpl), auth/ (PasswordHasher, SessionManager)
DI Layer di/ Mengelola dependency injection secara manual melalui pola Service Locator: membangun dan menyediakan instance API, database, repository, dan session. ServiceLocator.kt

Berikut adalah struktur folder lengkap proyek MovFlix:

MovFlix/app/src/main/kotlin/com/example/moviecatalogue/
├── MainActivity.kt                     ← Entry point aplikasi
├── MovieCatalogueApp.kt                ← Application class
├── SplashScreen.kt                     ← Layar splash animasi logo
├── data/                               ← DATA LAYER
│   ├── auth/
│   │   ├── PasswordHasher.kt           ← Hashing SHA-256 + salt
│   │   └── SessionManager.kt           ← SharedPreferences session
│   ├── local/
│   │   ├── MovieDao.kt                 ← DAO operasi watchlist
│   │   ├── MovieDatabase.kt            ← Konfigurasi Room (Singleton)
│   │   ├── MovieEntity.kt              ← Entity tabel watchlist + Converters
│   │   ├── UserDao.kt                  ← DAO operasi akun pengguna
│   │   └── UserEntity.kt               ← Entity tabel users
│   ├── remote/
│   │   ├── ApiService.kt               ← Interface Retrofit endpoint TMDB
│   │   └── Dtos.kt                     ← Data Transfer Objects (JSON mapping)
│   └── repository/
│       ├── AuthRepositoryImpl.kt       ← Implementasi autentikasi lokal
│       └── MovieRepositoryImpl.kt      ← Implementasi akses data film
├── di/
│   └── ServiceLocator.kt              ← Manual Dependency Injection
├── domain/                             ← DOMAIN LAYER
│   ├── AuthRepository.kt              ← Interface kontrak autentikasi
│   ├── Movie.kt                       ← Domain model (Movie, Genre, dll)
│   ├── MovieRepository.kt            ← Interface kontrak data film
│   └── UserSession.kt                ← Model sesi pengguna
└── ui/                                 ← UI LAYER
    ├── components/
    │   ├── MovieCard.kt               ← Kartu film reusable
    │   ├── MovieSlider.kt             ← Hero slider infinite scroll
    │   └── YouTubeTrailerPlayer.kt    ← WebView embed YouTube
    ├── navigation/
    │   └── Navigation.kt              ← NavHost + route definitions
    ├── screens/
    │   ├── auth/  (LoginScreen, RegisterScreen, AuthViewModel)
    │   ├── detail/ (DetailScreen, DetailViewModel)
    │   ├── home/   (HomeScreen, HomeViewModel)
    │   ├── profile/ (ProfileScreen, ProfileViewModel)
    │   └── search/ (SearchScreen, SearchViewModel)
    └── theme/
        ├── Color.kt                   ← Palet warna Netflix-inspired
        ├── Theme.kt                   ← Material 3 dark theme
        └── Typography.kt             ← Tipografi kustom

7. Implementasi Room Database pada Proyek

Room Database pada proyek MovFlix disimpan di dalam folder data/local/ dan digunakan untuk dua keperluan utama: (1) menyimpan watchlist film per akun pengguna, dan (2) menyimpan data akun pengguna yang telah terdaftar. Berikut adalah pemetaan komponen Room dan fungsinya:

Komponen Room File Fungsi Utama
Entity data/local/MovieEntity.kt Tabel watchlist_movies: menyimpan film favorit dengan composite primary key [id, userId] sehingga watchlist terpisah per akun. Memiliki TypeConverters untuk konversi List<Int> dan List<Genre> ke JSON string.
data/local/UserEntity.kt Tabel users: menyimpan data akun (id, name, email, passwordHash, salt) dengan index unik pada kolom email untuk mencegah duplikasi.
DAO data/local/MovieDao.kt
  • getAllWatchlistMovies(userId)Flow<List<MovieEntity>>: mengambil semua watchlist secara reaktif.
  • isInWatchlist(movieId, userId) → cek apakah film sudah ada di watchlist.
  • isInWatchlistFlow(movieId, userId) → versi reaktif Flow untuk observasi real-time.
  • insertMovie(movie) → menambahkan film ke watchlist (Replace on conflict).
  • deleteMovieById(movieId, userId) → menghapus film dari watchlist.
  • getWatchlistCount(userId) → menghitung jumlah film dalam watchlist.
data/local/UserDao.kt
  • insert(user) → mendaftarkan akun baru (Abort on conflict jika email duplikat).
  • getByEmail(email) → mencari user berdasarkan email untuk proses login.
  • countByEmail(email) → mengecek apakah email sudah terdaftar.
Database data/local/MovieDatabase.kt Konfigurasi Room Database dengan nama movflix_db, versi 3, entities = [MovieEntity, UserEntity]. Menggunakan pola Singleton (@Volatile + synchronized) untuk satu instance sepanjang masa hidup aplikasi. Menyediakan akses ke movieDao() dan userDao().
// Contoh MovieDao — operasi CRUD watchlist
@Dao
interface MovieDao {
    @Query("SELECT * FROM watchlist_movies WHERE userId = :userId ORDER BY addedAt DESC")
    fun getAllWatchlistMovies(userId: Int): Flow<List<MovieEntity>>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertMovie(movie: MovieEntity)

    @Query("DELETE FROM watchlist_movies WHERE id = :movieId AND userId = :userId")
    suspend fun deleteMovieById(movieId: Int, userId: Int)

    @Query("SELECT EXISTS(SELECT 1 FROM watchlist_movies WHERE id = :movieId AND userId = :userId)")
    suspend fun isInWatchlist(movieId: Int, userId: Int): Boolean
}

8. Implementasi REST API & Backend pada Proyek

Proyek MovFlix menggunakan TMDB (The Movie Database) API sebagai sumber data film utama. Koneksi ke backend API diimplementasikan menggunakan Retrofit 2 sebagai HTTP client, OkHttp untuk manajemen koneksi dan interceptor, serta Gson untuk serialisasi/deserialisasi JSON. Seluruh kode terkait API disimpan di folder data/remote/.

File Fungsi Utama Method / Endpoint
data/remote/ApiService.kt Interface Retrofit yang mendefinisikan semua endpoint TMDB API
Ambil film trending mingguan @GET("trending/movie/week") suspend fun getTrendingMovies()
Ambil film sedang tayang @GET("movie/now_playing") suspend fun getNowPlayingMovies()
Ambil film populer @GET("movie/popular") suspend fun getPopularMovies()
Ambil film rating tertinggi @GET("movie/top_rated") suspend fun getTopRatedMovies()
Pencarian film berdasarkan keyword @GET("search/movie") suspend fun searchMovies(query)
Ambil detail film lengkap @GET("movie/{movie_id}") suspend fun getMovieDetail(movieId)
Ambil daftar genre film @GET("genre/movie/list") suspend fun getGenres()
data/remote/Dtos.kt Data Transfer Objects untuk mapping JSON response ke objek Kotlin MovieDTO, MovieDetailDTO, GenreDTO, VideoDTO — masing-masing memiliki method toDomain()
di/ServiceLocator.kt Membangun instance Retrofit, OkHttp client, dan menyisipkan Bearer Token autentikasi TMDB pada setiap request buildApiService(), buildOkHttpClient() dengan auth interceptor
// Konfigurasi OkHttp dengan auth interceptor di ServiceLocator.kt
private fun buildOkHttpClient(): OkHttpClient {
    val authInterceptor = Interceptor { chain ->
        val request = chain.request().newBuilder()
            .header("Authorization", "Bearer ${BuildConfig.TMDB_BEARER_TOKEN}")
            .header("accept", "application/json")
            .build()
        chain.proceed(request)
    }
    return OkHttpClient.Builder()
        .addInterceptor(authInterceptor)
        .addInterceptor(loggingInterceptor)
        .connectTimeout(30, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .build()
}

// Retrofit instance
Retrofit.Builder()
    .baseUrl("https://api.themoviedb.org/3/")
    .client(okHttpClient)
    .addConverterFactory(GsonConverterFactory.create())
    .build()
    .create(ApiService::class.java)

Bagian 3: Komponen UI, Siklus Hidup, dan Fitur Aplikasi

9. Siklus Hidup Komponen (Activity Lifecycle)

File MainActivity.kt adalah titik masuk utama (entry point) aplikasi MovFlix. Ini adalah satu-satunya Activity dalam seluruh aplikasi karena MovFlix menerapkan arsitektur Single Activity di mana semua layar dikelola oleh Jetpack Navigation Compose di dalam satu Activity. Pada MovFlix, MainActivity mewarisi dari ComponentActivity dan hanya mengimplementasikan satu fungsi siklus hidup: onCreate().

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 1. Mengaktifkan edge-to-edge display untuk tampilan immersive
        enableEdgeToEdge()

        // 2. Mendapatkan instance repository dari ServiceLocator
        val repository = ServiceLocator.provideRepository(applicationContext)
        val authRepository = ServiceLocator.provideAuthRepository(applicationContext)

        // 3. Merender seluruh UI menggunakan Jetpack Compose
        setContent {
            MovFlixTheme {
                Surface(modifier = Modifier.fillMaxSize()) {
                    AppNavigation(
                        repository = repository,
                        authRepository = authRepository
                    )
                }
            }
        }
    }
}

Penjelasan fungsi siklus hidup yang relevan:

  • onCreate(savedInstanceState: Bundle?): Dipanggil saat Activity pertama kali dibuat. Pada MovFlix, fungsi ini melakukan tiga hal kritis: (1) mengaktifkan enableEdgeToEdge() agar UI membentang penuh hingga ke area status bar dan navigation bar, (2) menginisialisasi dependency (repository) melalui ServiceLocator, dan (3) memanggil setContent { } untuk merender seluruh pohon UI Compose beserta tema dan navigasi.

  • onStart(): Dipanggil saat Activity menjadi visible. Pada MovFlix, tidak di-override secara eksplisit karena Compose menangani visibilitas UI secara otomatis melalui recomposition.

  • onResume(): Dipanggil saat Activity berada di foreground dan siap menerima interaksi pengguna. Tidak perlu di-override karena StateFlow di ViewModel secara otomatis aktif selama composable di-collect.

  • onDestroy(): Dipanggil saat Activity dihancurkan. Pada arsitektur MVVM dengan Compose, ViewModel bertahan melewati onDestroy yang disebabkan oleh perubahan konfigurasi, sehingga data tidak hilang saat rotasi layar.

Selain MainActivity, terdapat juga MovieCatalogueApp.kt yang mewarisi Application. Kelas ini didaftarkan di AndroidManifest.xml dengan atribut android:name=".MovieCatalogueApp" dan bertugas sebagai tempat inisialisasi tingkat aplikasi. ServiceLocator diinisialisasi secara lazy pada saat pertama kali dipanggil.


10. Komponen Antarmuka Pengguna (UI Components)

Seluruh antarmuka pengguna pada proyek MovFlix dibangun menggunakan Jetpack Compose dengan paradigma deklaratif. Berikut adalah daftar lengkap komponen UI yang diterapkan beserta penjelasannya:

Komponen UI File Fungsi / Composable Penerapan
Splash Screen SplashScreen.kt @Composable SplashScreen() Layar pembuka dengan logo MovFlix, animasi fade-in + scale selama 2 detik sebelum navigasi ke login atau main screen.
Hero Slider (Infinite Loop) ui/components/MovieSlider.kt @Composable MovieSlider() Carousel film trending menggunakan HorizontalPager dengan infinite scroll (virtual page count = Int.MAX_VALUE) dan auto-scroll setiap 4 detik. Menampilkan backdrop film dengan gradient scrim dan dot indicator.
Movie Card ui/components/MovieCard.kt @Composable MovieCard() Kartu film reusable berukuran 130dp lebar dengan poster (AsyncImage + Coil), judul, dan rating bintang. Clickable untuk navigasi ke detail.
LazyRow (Horizontal Scroll) ui/screens/home/HomeScreen.kt CategoryRow() Baris horizontal berisi daftar MovieCard per kategori (Now Playing, Popular, Top Rated). Menggunakan LazyRow untuk performa optimal.
LazyVerticalGrid ui/screens/search/SearchScreen.kt SearchScreen() Grid 3 kolom untuk menampilkan hasil pencarian film dengan poster dan judul. Juga digunakan di ProfileScreen untuk menampilkan watchlist.
Shimmer Skeleton ui/screens/home/HomeScreen.kt shimmerBrush(), SliderSkeleton(), RowSkeleton() Efek loading skeleton yang bergerak (shimmer) saat data sedang dimuat, memberikan pengalaman visual yang halus dibanding loading spinner biasa.
YouTube Trailer Player ui/components/YouTubeTrailerPlayer.kt @Composable YouTubeTrailerPlayer() Memutar trailer YouTube langsung di dalam aplikasi menggunakan WebView dengan iframe embed. Mendukung fullscreen.
HorizontalPager (Tab Navigation) ui/navigation/Navigation.kt MainPagerScreen() Navigasi antar tab (Home, Search, Profile) menggunakan swipe gesture melalui HorizontalPager yang disinkronkan dengan NavigationBar.
Filter Chips (Genre) ui/screens/search/SearchScreen.kt FilterChip() dalam FlowRow() Chip genre yang dapat dipilih untuk memfilter pencarian film. Menggunakan FlowRow agar chip otomatis turun baris saat penuh.
NavigationBar (Bottom Nav) ui/navigation/Navigation.kt NavigationBar + NavigationBarItem Bottom navigation bar dengan 3 item: Home, Search, Profile, disinkronkan dengan HorizontalPager.

11. Pengelolaan Data Dinamis (StateFlow)

Proyek MovFlix menggunakan Kotlin StateFlow sebagai mekanisme utama untuk mengelola dan mengalirkan data secara reaktif dari ViewModel ke UI (Compose). StateFlow adalah bagian dari Kotlin Coroutines Flow API yang bersifat hot stream — selalu menyimpan nilai terakhir dan langsung mengirimkan pembaruan ke semua collector.

Pola implementasi StateFlow dalam proyek ini:

// Di ViewModel — mendefinisikan dan mengupdate state
class HomeViewModel(private val repo: MovieRepository) : ViewModel() {
    // State privat yang bisa diubah
    private val _trending = MutableStateFlow<List<Movie>>(emptyList())
    // State publik yang hanya bisa dibaca oleh UI
    val trending = _trending.asStateFlow()

    init { load() }

    private fun load() {
        viewModelScope.launch {
            repo.getTrendingMovies().fold(
                onSuccess = { _trending.value = it },   // Update state
                onFailure = { _error.value = it.message }
            )
        }
    }
}

// Di Compose Screen — mengobservasi state
@Composable
fun HomeScreen(vm: HomeViewModel, onMovieClick: (Int) -> Unit) {
    // collectAsState() mengubah StateFlow menjadi Compose State
    // UI otomatis recompose saat nilai berubah!
    val trending by vm.trending.collectAsState()

    if (trending.isEmpty()) SliderSkeleton()
    else MovieSlider(movies = trending, onMovieClick = onMovieClick)
}

Penerapan StateFlow pada setiap ViewModel:

  • HomeViewModel: Memiliki 5 StateFlow — trending, nowPlaying, popular, topRated, dan error. Keempat kategori film di-load secara paralel pada init { }.

  • SearchViewModel: Memiliki StateFlow untuk query, results, loading, genres, dan selectedGenre. Yang istimewa, SearchViewModel menerapkan debounce 400ms pada query input menggunakan _query.debounce(400) dan combine() dengan genre filter. Ini berarti pencarian baru dilakukan setelah pengguna berhenti mengetik selama 400ms, mengurangi jumlah API call yang tidak perlu.

  • DetailViewModel: Menggunakan sealed interface DetailUiState dengan tiga state: Loading, Success(movie), dan Error(msg). Pola ini memastikan UI hanya bisa berada di salah satu dari tiga kondisi tersebut.

  • AuthViewModel: Memiliki StateFlow loginError dan registerError untuk menampilkan pesan error validasi secara real-time.

Selain StateFlow, Room Database juga mengembalikan Flow<List<MovieEntity>> untuk watchlist, sehingga ketika data watchlist berubah (film ditambah/dihapus), UI ProfileScreen secara otomatis diperbarui tanpa perlu refresh manual.


12. Navigasi Aplikasi (Navigation)

Proyek MovFlix menggunakan Jetpack Navigation Compose untuk mengatur perpindahan antar layar. Seluruh konfigurasi navigasi terpusat di file ui/navigation/Navigation.kt yang mendefinisikan NavHost, route-route, dan logika perpindahan.

Daftar route yang didefinisikan:

Route Screen Deskripsi
"splash"SplashScreenLayar pembuka (startDestination). Setelah 2 detik, navigasi ke login atau main berdasarkan status sesi.
"login"LoginScreenHalaman login. Setelah berhasil, navigasi ke main dengan popUpTo agar tidak bisa kembali.
"register"RegisterScreenHalaman registrasi. Setelah berhasil, kembali ke halaman login via popBackStack().
"main"MainPagerScreenLayar utama berisi HorizontalPager dengan 3 tab: Home, Search, Profile.
"detail/{movieId}"DetailScreenHalaman detail film. Menerima parameter movieId: Int untuk mengambil data detail film dari API.

Alur navigasi aplikasi:

Splash Screen (2 detik)
    ├── [Belum login] ──→ Login Screen
    │                        ├── [Login berhasil] ──→ Main Screen (Home)
    │                        ├── [Guest] ──→ Main Screen (Home)
    │                        └── [Daftar] ──→ Register Screen ──→ Login Screen
    │
    └── [Sudah login] ──→ Main Screen
                            ├── Tab Home ←──swipe──→ Tab Search ←──swipe──→ Tab Profile
                            ├── [Klik film] ──→ Detail Screen ──→ [Back] ──→ Main
                            └── [Logout] ──→ Login Screen

Yang unik pada MovFlix adalah navigasi antar tab (Home, Search, Profile) tidak menggunakan Navigation Component biasa, melainkan menggunakan HorizontalPager yang disinkronkan dengan NavigationBar. Hal ini memungkinkan pengguna berpindah tab dengan gesture swipe selain menekan tombol bottom navigation, memberikan pengalaman navigasi yang lebih cepat dan modern. ViewModel di-scope ke composable "main" sehingga tetap hidup saat berpindah tab.


Bagian 4: Manajemen Proyek, Kualitas, dan Kesimpulan

13. Manajemen Dependensi (Gradle)

Gradle adalah sistem build otomatis yang digunakan oleh Android Studio untuk mengelola proses kompilasi, pengemasan, dan manajemen dependensi proyek. Pada proyek MovFlix, terdapat dua file Gradle utama:

  • build.gradle.kts (Project/Root level): Mendefinisikan plugin dan konfigurasi yang berlaku untuk seluruh sub-proyek. Pada MovFlix, file ini mendaftarkan plugin Android Application, Kotlin Android, Kotlin Compose, dan KSP (Kotlin Symbol Processing untuk Room compiler).

  • app/build.gradle.kts (Module/App level): Mendefinisikan konfigurasi spesifik modul app, termasuk compileSdk, minSdk, targetSdk, versionCode, BuildConfig fields (API Key TMDB), dan daftar lengkap dependensi pihak ketiga.

Berikut adalah daftar dependensi pihak ketiga yang digunakan pada proyek MovFlix beserta fungsinya:

Library Fungsi
Retrofit 2 + Gson ConverterHTTP client type-safe untuk komunikasi dengan TMDB API. Gson mengkonversi JSON response ke data class Kotlin.
OkHttp + Logging InterceptorHTTP engine yang dipakai Retrofit. Logging interceptor untuk debugging request/response di Logcat. Auth interceptor untuk menyisipkan Bearer Token TMDB.
Room Runtime + KTX + Compiler (KSP)Database lokal untuk menyimpan watchlist dan data akun pengguna. Room KTX menambahkan dukungan Coroutines.
Kotlin Coroutines AndroidPemrograman asinkron untuk operasi jaringan dan database tanpa memblokir UI thread.
Coil ComposeLibrary image loading modern untuk Kotlin/Compose. Memuat poster dan backdrop film dari URL TMDB secara asinkron dengan caching.
Navigation ComposeJetpack Navigation untuk mengatur perpindahan antar screen di Compose (NavHost, NavController, route).
Lifecycle ViewModel ComposeIntegrasi ViewModel dengan Compose. Menyediakan viewModel() composable function.
Lifecycle Runtime ComposeMenyediakan collectAsStateWithLifecycle() untuk observasi yang lifecycle-aware.
Material Icons ExtendedKoleksi icon Material Design yang lebih lengkap (Search, Person, Bookmark, ArrowBack, ExitToApp, dll).
YouTube PlayerLibrary untuk memutar video trailer YouTube langsung di dalam aplikasi.
Jetpack Compose (BOM)Bill of Materials yang memastikan semua library Compose menggunakan versi yang kompatibel.
// Contoh dependensi di app/build.gradle.kts
dependencies {
    // Retrofit + Networking
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    implementation("com.squareup.retrofit2:converter-gson:2.9.0")
    implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")

    // Room Database
    implementation("androidx.room:room-runtime:2.6.1")
    implementation("androidx.room:room-ktx:2.6.1")
    ksp("androidx.room:room-compiler:2.6.1")     // KSP untuk code generation

    // Image Loading
    implementation("io.coil-kt:coil-compose:2.6.0")

    // Navigation
    implementation("androidx.navigation:navigation-compose:2.7.7")
}

14. Penanganan Error (Error Handling) & Kondisi Khusus

Proyek MovFlix menerapkan penanganan error secara berlapis (layered error handling) mulai dari lapisan Data (Repository) hingga lapisan UI (Compose Screen). Berikut adalah penjelasan untuk berbagai skenario error dan edge case:

1. Kegagalan Koneksi Internet saat Memanggil API:

  • Di MovieRepositoryImpl, semua panggilan API dibungkus dalam method safeApiCall() yang menggunakan try-catch. Jika koneksi gagal, IOException ditangkap dan dikembalikan sebagai Result.failure(e).

  • Di ViewModel, Result.fold(onSuccess, onFailure) digunakan untuk memisahkan jalur sukses dan error. Saat gagal, pesan error disimpan di StateFlow _error.

  • Di UI, HomeScreen menampilkan pesan error dalam teks berwarna merah. DetailScreen menampilkan pesan error di tengah layar melalui state DetailUiState.Error(msg).

// Data Layer: safeApiCall di MovieRepositoryImpl
private suspend fun <T> safeApiCall(call: suspend () -> T): Result<T> {
    return try {
        Result.success(call())
    } catch (e: Exception) {
        Result.failure(e)    // Menangkap IOException, HttpException, dll
    }
}

// ViewModel Layer: Penanganan Result
repo.getTrendingMovies().fold(
    onSuccess = { _trending.value = it },
    onFailure = { _error.value = it.message }    // "Unable to resolve host"
)

2. Database Kosong (Watchlist Belum Ada Isinya):

  • Pada ProfileScreen, terdapat pengecekan eksplisit: if (watchlist.isEmpty()). Jika watchlist kosong, UI menampilkan teks "Watchlist kosong" dengan warna semi-transparan di tengah layar, bukan halaman putih kosong.

3. Pengguna Guest Mencoba Menyimpan Watchlist:

  • Jika pengguna masuk sebagai Guest dan membuka tab Profile, alih-alih menampilkan watchlist, UI menampilkan pesan "Login untuk menyimpan watchlist" beserta tombol "Login" yang mengarahkan ke halaman login. Pada level Repository, operasi addToWatchlist() dan removeFromWatchlist() langsung di-return tanpa aksi jika pengguna adalah Guest (if (isGuest) return).

4. Validasi Input pada Registrasi:

  • Pada AuthRepositoryImpl, sebelum mendaftarkan akun baru, dilakukan validasi: email harus mengandung @, password minimal 6 karakter, dan email tidak boleh sudah terdaftar. Jika gagal, Result.failure dengan pesan yang jelas (misal "Email sudah terdaftar", "Password salah") dikembalikan dan ditampilkan di UI.

5. Film Tanpa Poster, Backdrop, atau Trailer:

  • Properti posterPath, backdropPath, dan trailerKey pada domain model Movie bersifat nullable. Di DetailScreen, bagian trailer hanya ditampilkan jika trailerKey != null: if (!movie.trailerKey.isNullOrBlank()) { ... }. Untuk gambar, AsyncImage dari Coil secara otomatis menampilkan placeholder saat URL null.

6. State Loading:

  • Semua layar yang memuat data menampilkan indikator loading yang sesuai. HomeScreen menggunakan shimmer skeleton yang elegan (bukan sekadar CircularProgressIndicator). SearchScreen dan DetailScreen menggunakan CircularProgressIndicator berwarna merah (brand color).


15. Fitur Utama & Prinsip Pemrograman Mobile

Fitur-fitur utama yang diimplementasikan pada aplikasi MovFlix:

No Fitur Deskripsi
1Autentikasi LokalRegister dan login dengan email & password (disimpan di Room, di-hash dengan SHA-256 + salt), serta mode Guest tanpa perlu akun.
2Beranda DinamisHero slider trending film dengan auto-scroll infinite loop, diikuti baris kategori Now Playing, Popular, dan Top Rated menggunakan LazyRow.
3Pencarian CerdasPencarian film secara debounced (400ms delay) tanpa tombol submit, dilengkapi filter genre chip yang dinamis dari API.
4Detail Film LengkapHalaman detail menampilkan backdrop, judul, rating, durasi, tanggal rilis, genre chips, sinopsis, dan pemutaran trailer YouTube fullscreen langsung di dalam aplikasi.
5Watchlist Per-AkunSimpan film favorit ke watchlist yang terikat pada akun masing-masing. Akun Guest diarahkan untuk login terlebih dahulu. Watchlist tersimpan secara offline di Room.
6Navigasi SwipeTransisi mulus antara layar Home, Search, dan Profile menggunakan HorizontalPager yang tersinkron dengan bottom navigation bar.
7Splash ScreenLayar pembuka animasi logo MovFlix selama 2 detik dengan efek fade-in dan scale-up menggunakan animateFloatAsState.
8Profil PenggunaHalaman profil menampilkan avatar, nama, email, dan grid watchlist pengguna. Tersedia tombol logout.
9Tema Gelap SinematikTema Material Design 3 dark mode dengan palet warna Netflix-inspired (merah #E50914, hitam #121212). Status bar transparan untuk tampilan immersive.
10Shimmer LoadingEfek shimmer skeleton saat data sedang dimuat, memberikan pengalaman visual yang lebih baik daripada loading spinner biasa.

Prinsip-prinsip pemrograman mobile yang berhasil diterapkan:

  • Responsiveness (Ketanggapan UI): Seluruh operasi berat (API call, database query) dijalankan di background thread menggunakan viewModelScope.launch { } (Kotlin Coroutines). UI thread tidak pernah diblokir, sehingga aplikasi tetap responsif saat memuat data. Penggunaan suspend function pada DAO dan API service memastikan operasi asinkron berjalan optimal.

  • Efisiensi Memori: Penggunaan LazyRow dan LazyVerticalGrid memastikan hanya item yang terlihat di layar yang di-render (virtualisasi). Coil menangani image caching secara otomatis, menghindari pemuatan ulang gambar yang sama. Room Database dengan pola Singleton mencegah pembuatan instance database ganda.

  • Kemudahan Navigasi: Arsitektur Single Activity dengan Jetpack Navigation Compose memberikan navigasi yang mulus dan dapat diprediksi. Penggunaan HorizontalPager yang tersinkronisasi dengan NavigationBar memungkinkan pengguna berpindah tab dengan cara yang paling intuitif: swipe atau tap.

  • Separation of Concerns: Arsitektur berlapis (UI, Domain, Data) memastikan setiap komponen memiliki tanggung jawab yang jelas. ViewModel tidak tahu tentang Compose, Repository tidak tahu tentang ViewModel, dan Domain layer sama sekali tidak bergantung pada framework Android. Ini membuat kode lebih mudah di-test, dipelihara, dan diperluas.

  • Offline Capability: Data watchlist dan akun pengguna disimpan secara lokal menggunakan Room Database, sehingga tetap tersedia meskipun tanpa koneksi internet. Sesi login dipertahankan menggunakan SharedPreferences melalui SessionManager.

  • Keamanan Data: Password pengguna tidak pernah disimpan dalam bentuk plain text. Aplikasi menggunakan SHA-256 hashing dengan random salt per-user melalui PasswordHasher. API key TMDB disimpan di BuildConfig (bukan hardcoded di source) dan dikirim melalui header Authorization: Bearer.

  • User Experience (UX) yang Baik: Shimmer skeleton saat loading (bukan layar kosong), debounced search (mengurangi request API yang tidak perlu), pull-to-refresh gesture, edge-to-edge display, animasi transisi halus antar tab, dan feedback visual yang jelas pada setiap interaksi pengguna.

  • Reusability Komponen: Komponen UI seperti MovieCard, MovieSlider, dan YouTubeTrailerPlayer dirancang sebagai composable yang reusable. MovieCard digunakan di HomeScreen (LazyRow), SearchScreen (grid), dan ProfileScreen (watchlist) dengan tampilan yang konsisten.


— Akhir Dokumentasi MovFlix —

Komentar

Postingan Populer