Movies Database
Repository Types
Now the basics of the repository
module.
DTOs
We create Data Transfer Objects, ActorDto
, MovieDto
, and RatingDto
to abstract and restrict
how the data is used. These DTOs are immutable, which will help Jetpack Compose optimize UI updates.
show in full file repository/src/main/java/com/androidbyexample/compose/movies/repository/ActorDto.kt
// ...
import com.androidbyexample.compose.movies.data.RoleWithMovie
data class ActorDto(
val id: String,
val name: String,
)
internal fun ActorEntity.toDto() =
// ...
show in full file repository/src/main/java/com/androidbyexample/compose/movies/repository/MovieDto.kt
// ...
import com.androidbyexample.compose.movies.data.RoleWithActor
data class MovieDto(
val id: String,
val title: String,
val description: String,
val ratingId: String,
)
internal fun MovieEntity.toDto() =
// ...
show in full file repository/src/main/java/com/androidbyexample/compose/movies/repository/RatingDto.kt
// ...
import com.androidbyexample.compose.movies.data.RatingWithMovies
data class RatingDto(
val id: String,
val name: String,
val description: String,
)
internal fun RatingEntity.toDto() =
// ...
We also define extension functions, such as these
show in full file repository/src/main/java/com/androidbyexample/compose/movies/repository/ActorDto.kt
// ...
)
internal fun ActorEntity.toDto() =
ActorDto(id = id, name = name)
internal fun ActorDto.toEntity() =
ActorEntity(id = id, name = name)
data class ActorWithFilmographyDto(
// ...
to convert between the entities defined in the data
module and the DTOs we expose from this
repository
module.
Note that the extension functions are marked internal
. This makes them accessible anywhere
inside the repository
module, but not outside the module.
MovieRepository
The MovieRepository
interface defines how we communicate with a repository.
This allows different repository implementations (later we'll add a web-service implementation).
Note that I'm not using vararg
in the MovieRepository
. This is because it simplifies our web
services implementation when we do it later.
show in full file repository/src/main/java/com/androidbyexample/compose/movies/repository/MovieRepository.kt
// ...
import kotlinx.coroutines.flow.Flow
interface MovieRepository {
val ratingsFlow: Flow<List<RatingDto>>
val moviesFlow: Flow<List<MovieDto>>
val actorsFlow: Flow<List<ActorDto>>
suspend fun getRatingWithMovies(id: String): RatingWithMoviesDto
suspend fun getMovieWithCast(id: String): MovieWithCastDto
suspend fun getActorWithFilmography(id: String): ActorWithFilmographyDto
suspend fun insert(movie: MovieDto)
suspend fun insert(actor: ActorDto)
suspend fun insert(rating: RatingDto)
suspend fun resetDatabase()
}
MovieDatabaseRepository
This is the concrete implementation of MovieRepository
that we use to work with the Room database.
Much of it is direct passthrough to the DAO.
show in full file repository/src/main/java/com/androidbyexample/compose/movies/repository/MovieDatabaseRepository.kt
// ...
import kotlinx.coroutines.flow.map
class MovieDatabaseRepository(
private val dao: MovieDao
): MovieRepository {
override val ratingsFlow =
dao.getRatingsFlow()
.map { ratings ->// for each List<RatingEntity> that's emitted
// create a list of RatingDto
ratings.map { rating -> rating.toDto() } // map each entity to Dto
}
override val moviesFlow =
dao.getMoviesFlow()
.map { movies ->
movies.map { it.toDto() }
}
override val actorsFlow =
dao.getActorsFlow()
.map { actors ->
actors.map { it.toDto() }
}
override suspend fun getRatingWithMovies(id: String): RatingWithMoviesDto =
dao.getRatingWithMovies(id).toDto()
override suspend fun getMovieWithCast(id: String): MovieWithCastDto =
dao.getMovieWithCast(id).toDto()
override suspend fun getActorWithFilmography(id: String): ActorWithFilmographyDto =
dao.getActorWithFilmography(id).toDto()
override suspend fun insert(movie: MovieDto) = dao.insert(movie.toEntity())
override suspend fun insert(actor: ActorDto) = dao.insert(actor.toEntity())
override suspend fun insert(rating: RatingDto) = dao.insert(rating.toEntity())
override suspend fun resetDatabase() = dao.resetDatabase()
}
The query functions that expose a Flow
transform the returned entities into DTOs.
The map
function on Flow
creates a new flow that calls the nested map
on the
list of entities to convert them into DTOs.
All code changes
ADDED: repository/src/main/java/com/androidbyexample/compose/movies/repository/ActorDto.kt
package com.androidbyexample.compose.movies.repository
import com.androidbyexample.compose.movies.data.ActorEntity
import com.androidbyexample.compose.movies.data.ActorWithFilmography
import com.androidbyexample.compose.movies.data.RoleWithMovie
data class ActorDto(
val id: String,
val name: String,
)
internal fun ActorEntity.toDto() =
ActorDto(id = id, name = name)
internal fun ActorDto.toEntity() =
ActorEntity(id = id, name = name)
data class ActorWithFilmographyDto(
val actor: ActorDto,
val filmography: List<RoleWithMovieDto>,
)
data class RoleWithMovieDto(
val movie: MovieDto,
val character: String,
val orderInCredits: Int,
)
internal fun RoleWithMovie.toDto() =
RoleWithMovieDto(
movie = movie.toDto(),
character = role.character,
orderInCredits = role.orderInCredits,
)
internal fun ActorWithFilmography.toDto() =
ActorWithFilmographyDto(
actor = actor.toDto(),
filmography =
rolesWithMovies.map {
it.toDto()
}
)
ADDED: repository/src/main/java/com/androidbyexample/compose/movies/repository/MovieDatabaseRepository.kt
package com.androidbyexample.compose.movies.repository
import android.content.Context
import com.androidbyexample.compose.movies.data.MovieDao
import com.androidbyexample.compose.movies.data.createDao
import kotlinx.coroutines.flow.map
class MovieDatabaseRepository(
private val dao: MovieDao
): MovieRepository {
override val ratingsFlow =
dao.getRatingsFlow()
.map { ratings ->// for each List<RatingEntity> that's emitted
// create a list of RatingDto
ratings.map { rating -> rating.toDto() } // map each entity to Dto
}
override val moviesFlow =
dao.getMoviesFlow()
.map { movies ->
movies.map { it.toDto() }
}
override val actorsFlow =
dao.getActorsFlow()
.map { actors ->
actors.map { it.toDto() }
}
override suspend fun getRatingWithMovies(id: String): RatingWithMoviesDto =
dao.getRatingWithMovies(id).toDto()
override suspend fun getMovieWithCast(id: String): MovieWithCastDto =
dao.getMovieWithCast(id).toDto()
override suspend fun getActorWithFilmography(id: String): ActorWithFilmographyDto =
dao.getActorWithFilmography(id).toDto()
override suspend fun insert(movie: MovieDto) = dao.insert(movie.toEntity())
override suspend fun insert(actor: ActorDto) = dao.insert(actor.toEntity())
override suspend fun insert(rating: RatingDto) = dao.insert(rating.toEntity())
override suspend fun resetDatabase() = dao.resetDatabase()
}
ADDED: repository/src/main/java/com/androidbyexample/compose/movies/repository/MovieDto.kt
package com.androidbyexample.compose.movies.repository
import com.androidbyexample.compose.movies.data.MovieEntity
import com.androidbyexample.compose.movies.data.MovieWithCast
import com.androidbyexample.compose.movies.data.RoleWithActor
data class MovieDto(
val id: String,
val title: String,
val description: String,
val ratingId: String,
)
internal fun MovieEntity.toDto() =
MovieDto(id = id, title = title, description = description, ratingId = ratingId)
internal fun MovieDto.toEntity() =
MovieEntity(id = id, title = title, description = description, ratingId = ratingId)
data class MovieWithCastDto(
val movie: MovieDto,
val cast: List<RoleWithActorDto>,
)
data class RoleWithActorDto(
val actor: ActorDto,
val character: String,
val orderInCredits: Int,
)
internal fun RoleWithActor.toDto() =
RoleWithActorDto(
actor = actor.toDto(),
character = role.character,
orderInCredits = role.orderInCredits,
)
internal fun MovieWithCast.toDto() =
MovieWithCastDto(
movie = movie.toDto(),
cast =
rolesWithActors.map {
it.toDto()
}
)
ADDED: repository/src/main/java/com/androidbyexample/compose/movies/repository/MovieRepository.kt
package com.androidbyexample.compose.movies.repository
import kotlinx.coroutines.flow.Flow
interface MovieRepository {
val ratingsFlow: Flow<List<RatingDto>>
val moviesFlow: Flow<List<MovieDto>>
val actorsFlow: Flow<List<ActorDto>>
suspend fun getRatingWithMovies(id: String): RatingWithMoviesDto
suspend fun getMovieWithCast(id: String): MovieWithCastDto
suspend fun getActorWithFilmography(id: String): ActorWithFilmographyDto
suspend fun insert(movie: MovieDto)
suspend fun insert(actor: ActorDto)
suspend fun insert(rating: RatingDto)
suspend fun resetDatabase()
}
ADDED: repository/src/main/java/com/androidbyexample/compose/movies/repository/RatingDto.kt
package com.androidbyexample.compose.movies.repository
import com.androidbyexample.compose.movies.data.RatingEntity
import com.androidbyexample.compose.movies.data.RatingWithMovies
data class RatingDto(
val id: String,
val name: String,
val description: String,
)
internal fun RatingEntity.toDto() =
RatingDto(id = id, name = name, description = description)
internal fun RatingDto.toEntity() =
RatingEntity(id = id, name = name, description = description)
data class RatingWithMoviesDto(
val rating: RatingDto,
val movies: List<MovieDto>,
)
// only need the toDto(); we don't use this to do database updates
internal fun RatingWithMovies.toDto() =
RatingWithMoviesDto(
rating = rating.toDto(),
movies = movies.map { it.toDto() },
)