- >
/**
* Deletes all existing movies and inserts a new list of movies in a single transaction.
*
* @param movies The new list of [MovieDTO] objects to insert.
*/
@Transaction
suspend fun refreshMovies(movies: List
- >
/**
* Fetches movies from the remote source and updates the local database.
*/
suspend fun refreshMovies()
/**
* Retrieves a specific movie by its ID.
*/
suspend fun getMovieById(id: Int): Movie?
}
### MovieRepositoryImpl.kt
The implementation of `MovieRepository` coordinates data flow between the `MovieApiService` and `MovieDao`.
kotlin
import com.benedek.movie_app_test.data.local.dao.MovieDao
import com.benedek.movie_app_test.data.remote.api.MovieApiService
import com.benedek.movie_app_test.domain.model.Movie
import com.benedek.movie_app_test.domain.repository.MovieRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
/**
* Implementation of the MovieRepository interface.
*/
class MovieRepositoryImpl @Inject constructor(
private val movieApiService: MovieApiService,
private val movieDao: MovieDao
) : MovieRepository {
/**
* Returns a flow of movies from the local database, mapping DTOs to domain models.
*/
override fun getMovies(): Flow
- > {
return movieDao.getAllMovies().map { entities ->
entities.map { it.toDomainModel() }
}
}
/**
* Fetches movies from the API and refreshes the local database.
*/
override suspend fun refreshMovies() {
try {
val response = movieApiService.getMovies()
if (response.isSuccessful && response.body() != null) {
movieDao.refreshMovies(response.body()!!)
}
} catch (e: Exception) {
// Error handling can be added here (e.g., logging)
}
}
/**
* Retrieves a single movie by ID from the local database.
*/
override suspend fun getMovieById(id: Int): Movie? {
return movieDao.getMovieById(id)?.toDomainModel()
}
}
---
## 5. Domain Layer
### Movie.kt (Domain Model)
This model represents the movie entity used within the application logic and UI.
kotlin
/**
* Domain model representing a movie.
*/
data class Movie(
val id: Int,
val title: String,
val releaseDate: String,
val overview: String,
val posterPath: String,
val voteAverage: Double
)
---
## 6. Dependency Injection with Hilt
### NetworkModule.kt
Configures Retrofit and OkHttpClient for network requests.
kotlin
import com.benedek.movie_app_test.data.remote.api.MovieApiService
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
private const val BASE_URL = "https://api.themoviedb.org/3/"
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient {
val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
return OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()
}
@Provides
@Singleton
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
@Provides
@Singleton
fun provideMovieApiService(retrofit: Retrofit): MovieApiService {
return retrofit.create(MovieApiService::class.java)
}
}
### DatabaseModule.kt
Configures the Room database and provides the `MovieDao`.
kotlin
import android.content.Context
import androidx.room.Room
import com.benedek.movie_app_test.data.local.dao.MovieDao
import com.benedek.movie_app_test.data.local.db.MovieDatabase
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {
@Provides
@Singleton
fun provideDatabase(@ApplicationContext context: Context): MovieDatabase {
return Room.databaseBuilder(
context,
MovieDatabase::class.java,
"movie_database"
).build()
}
@Provides
fun provideMovieDao(database: MovieDatabase): MovieDao {
return database.movieDao()
}
}
### RepositoryModule.kt
Binds the `MovieRepository` interface to its implementation.
kotlin
import com.benedek.movie_app_test.data.repository.MovieRepositoryImpl
import com.benedek.movie_app_test.domain.repository.MovieRepository
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
@Binds
@Singleton
abstract fun bindMovieRepository(
movieRepositoryImpl: MovieRepositoryImpl
): MovieRepository
}
---
## 7. ViewModel
### MovieListViewModel.kt
Manages the UI state for the movie list screen.
kotlin
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.benedek.movie_app_test.domain.model.Movie
import com.benedek.movie_app_test.domain.repository.MovieRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
/**
* ViewModel for managing the movie list.
*/
@HiltViewModel
class MovieListViewModel @Inject constructor(
private val repository: MovieRepository
) : ViewModel() {
private val _movies = MutableStateFlow
- >(emptyList())
val movies: StateFlow
- > = _movies
init {
loadMovies()
}
private fun loadMovies() {
viewModelScope.launch {
repository.getMovies().collect { movieList ->
_movies.value = movieList
}
}
viewModelScope.launch {
repository.refreshMovies()
}
}
}
---
## 8. UI Components (Jetpack Compose)
### MovieListScreen.kt
The main screen displaying a list of movies using `LazyColumn`.
kotlin
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import coil.compose.AsyncImage
import com.benedek.movie_app_test.domain.model.Movie
/**
* Composable for displaying a list of movies.
*/
@Composable
fun MovieListScreen(
onMovieClick: (Int) -> Unit,
viewModel: MovieListViewModel = hiltViewModel()
) {
val movies by viewModel.movies.collectAsState()
Scaffold(
topBar = {
Small



