Skip to content

Commit

Permalink
Merge pull request #126 from VictorKabata/fix-clean-ups
Browse files Browse the repository at this point in the history
Migration to Result<>
  • Loading branch information
VictorKabata authored Sep 23, 2024
2 parents abd96db + fd98079 commit 63504cd
Show file tree
Hide file tree
Showing 19 changed files with 177 additions and 272 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ interface FavoriteMovieDao {
suspend fun saveFavoriteMovie(movie: MovieDetailsEntity)

@Query("SELECT * FROM `Favorite Movies`")
fun getAllFavoriteMovies(): Flow<List<MovieDetailsEntity>>
fun getAllFavoriteMovies(): Flow<List<MovieDetailsEntity>?>

@Query("SELECT * FROM `Favorite Movies` WHERE id = :id")
suspend fun getFavoriteMovie(id: Int): MovieDetailsEntity
fun getFavoriteMovie(id: Int): Flow<MovieDetailsEntity?>

@Query("DELETE FROM `Favorite Movies` WHERE id = :id")
suspend fun deleteFavoriteMovie(id: Int)
Expand All @@ -25,5 +25,5 @@ interface FavoriteMovieDao {
suspend fun deleteAllFavoriteMovies()

@Query("SELECT COUNT() FROM `Favorite Movies` WHERE id = :id")
fun isMovieFavorite(id: Int): Flow<Boolean>
fun isMovieFavorite(id: Int): Flow<Boolean?>
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ class FavoritesRepositoryImpl(
private val appDatabase: AppDatabase
) : FavoritesRepository {

override suspend fun getFavouriteMovies(): Flow<List<MovieDetails>> {
return appDatabase.favoriteMovieDao().getAllFavoriteMovies().map {
it.map { movieDetail -> movieDetail.toDomain() }
override suspend fun getFavouriteMovies(): Result<Flow<List<MovieDetails>?>> {
return runCatching {
appDatabase.favoriteMovieDao().getAllFavoriteMovies().map {
it?.map { movieDetail -> movieDetail.toDomain() }
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,80 +11,67 @@ import com.vickbt.composeApp.domain.models.Cast
import com.vickbt.composeApp.domain.models.Movie
import com.vickbt.composeApp.domain.models.MovieDetails
import com.vickbt.composeApp.domain.repositories.MovieDetailsRepository
import com.vickbt.composeApp.utils.ResultState
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.get
import io.ktor.client.request.parameter
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map

class MovieDetailsRepositoryImpl(
private val httpClient: HttpClient,
private val appDatabase: AppDatabase
) : MovieDetailsRepository {

override suspend fun fetchMovieDetails(movieId: Int): Flow<ResultState<MovieDetails>> {
val isMovieCached = isMovieFavorite(movieId = movieId).firstOrNull()
override suspend fun fetchMovieDetails(movieId: Int): Result<Flow<MovieDetails?>> {
val isMovieCached = isMovieFavorite(movieId = movieId).getOrDefault(flowOf(false))
.firstOrNull()

return if (isMovieCached == true) {
try {
val cachedFavoriteMovie = getFavoriteMovie(movieId = movieId)
flowOf(ResultState.Success(data = cachedFavoriteMovie))
} catch (e: Exception) {
flowOf(ResultState.Failure(exception = e))
}
getFavoriteMovie(movieId = movieId)
} else {
flowOf(
safeApiCall {
val response =
httpClient.get(urlString = "movie/$movieId").body<MovieDetailsDto>()
response.toDomain()
}
)
safeApiCall {
httpClient.get(urlString = "movie/$movieId").body<MovieDetailsDto>().toDomain()
}
}
}

override suspend fun fetchMovieCast(movieId: Int): Flow<ResultState<Cast>> {
return flowOf(
safeApiCall {
val response = httpClient.get(urlString = "movie/$movieId/credits").body<CastDto>()

response.toDomain()
}
)
override suspend fun fetchMovieCast(movieId: Int): Result<Flow<Cast>> {
return safeApiCall {
httpClient.get(urlString = "movie/$movieId/credits").body<CastDto>().toDomain()
}
}

override suspend fun fetchSimilarMovies(
movieId: Int,
page: Int
): Flow<ResultState<List<Movie>?>> {
return flowOf(
safeApiCall {
val response = httpClient.get(urlString = "movie/$movieId/similar") {
parameter("page", page)
}.body<MovieResultsDto>()
): Result<Flow<List<Movie>?>> {
return safeApiCall {
val response = httpClient.get(urlString = "movie/$movieId/similar") {
parameter("page", page)
}.body<MovieResultsDto>()

response.movies?.map { it.toDomain() }
}
)
response.movies?.map { it.toDomain() }
}
}

override suspend fun saveFavoriteMovie(movie: MovieDetails) {
appDatabase.favoriteMovieDao().saveFavoriteMovie(movie = movie.toEntity())
runCatching { appDatabase.favoriteMovieDao().saveFavoriteMovie(movie = movie.toEntity()) }
}

override suspend fun getFavoriteMovie(movieId: Int): MovieDetails {
val favMovieDao = appDatabase.favoriteMovieDao()
return favMovieDao.getFavoriteMovie(id = movieId).toDomain()
override suspend fun getFavoriteMovie(movieId: Int): Result<Flow<MovieDetails?>> {
return runCatching {
appDatabase.favoriteMovieDao().getFavoriteMovie(id = movieId).map { it?.toDomain() }
}
}

override suspend fun deleteFavoriteMovie(movieId: Int) {
appDatabase.favoriteMovieDao().deleteFavoriteMovie(id = movieId)
runCatching { appDatabase.favoriteMovieDao().deleteFavoriteMovie(id = movieId) }
}

override suspend fun isMovieFavorite(movieId: Int): Flow<Boolean> {
return appDatabase.favoriteMovieDao().isMovieFavorite(id = movieId)
override suspend fun isMovieFavorite(movieId: Int): Result<Flow<Boolean?>> {
return runCatching { appDatabase.favoriteMovieDao().isMovieFavorite(id = movieId) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,67 +5,57 @@ import com.vickbt.composeApp.data.network.models.MovieResultsDto
import com.vickbt.composeApp.data.network.utils.safeApiCall
import com.vickbt.composeApp.domain.models.Movie
import com.vickbt.composeApp.domain.repositories.MoviesRepository
import com.vickbt.composeApp.utils.ResultState
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.get
import io.ktor.client.request.parameter
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf

class MoviesRepositoryImpl constructor(
class MoviesRepositoryImpl(
private val httpClient: HttpClient
) : MoviesRepository {

override suspend fun fetchNowPlayingMovies(page: Int): Flow<ResultState<List<Movie>?>> {
return flowOf(
safeApiCall {
val response = httpClient.get(urlString = "movie/now_playing") {
parameter("page", page)
}.body<MovieResultsDto>()
override suspend fun fetchNowPlayingMovies(page: Int): Result<Flow<List<Movie>?>> {
return safeApiCall {
val response = httpClient.get(urlString = "movie/now_playing") {
parameter("page", page)
}.body<MovieResultsDto>()

response.movies?.map { it.toDomain() }
}
)
response.movies?.map { it.toDomain() }
}
}

override suspend fun fetchTrendingMovies(
mediaType: String,
timeWindow: String,
page: Int
): Flow<ResultState<List<Movie>?>> {
return flowOf(
safeApiCall {
val response = httpClient.get(urlString = "trending/$mediaType/$timeWindow") {
parameter("page", page)
}.body<MovieResultsDto>()

response.movies?.map { it.toDomain() }
}
)
): Result<Flow<List<Movie>?>> {
return safeApiCall {
val response = httpClient.get(urlString = "trending/$mediaType/$timeWindow") {
parameter("page", page)
}.body<MovieResultsDto>()

response.movies?.map { it.toDomain() }
}
}

override suspend fun fetchPopularMovies(page: Int): Flow<ResultState<List<Movie>?>> {
return flowOf(
safeApiCall {
val response = httpClient.get(urlString = "movie/popular") {
parameter("page", page)
}.body<MovieResultsDto>()
override suspend fun fetchPopularMovies(page: Int): Result<Flow<List<Movie>?>> {
return safeApiCall {
val response = httpClient.get(urlString = "movie/popular") {
parameter("page", page)
}.body<MovieResultsDto>()

response.movies?.map { it.toDomain() }
}
)
response.movies?.map { it.toDomain() }
}
}

override suspend fun fetchUpcomingMovies(page: Int): Flow<ResultState<List<Movie>?>> {
return flowOf(
safeApiCall {
val response = httpClient.get(urlString = "movie/upcoming") {
parameter("page", page)
}.body<MovieResultsDto>()
override suspend fun fetchUpcomingMovies(page: Int): Result<Flow<List<Movie>?>> {
return safeApiCall {
val response = httpClient.get(urlString = "movie/upcoming") {
parameter("page", page)
}.body<MovieResultsDto>()

response.movies?.map { it.toDomain() }
}
)
response.movies?.map { it.toDomain() }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ import com.vickbt.composeApp.data.network.models.MovieResultsDto
import com.vickbt.composeApp.data.network.utils.safeApiCall
import com.vickbt.composeApp.domain.models.Movie
import com.vickbt.composeApp.domain.repositories.SearchRepository
import com.vickbt.composeApp.utils.ResultState
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.get
import io.ktor.client.request.parameter
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf

class SearchRepositoryImpl(
private val httpClient: HttpClient
Expand All @@ -20,16 +18,14 @@ class SearchRepositoryImpl(
override suspend fun searchMovie(
movieName: String,
page: Int
): Flow<ResultState<List<Movie>?>> {
return flowOf(
safeApiCall {
val response = httpClient.get(urlString = "search/movie") {
parameter("query", movieName)
parameter("page", page)
}.body<MovieResultsDto>()
): Result<Flow<List<Movie>?>> {
return safeApiCall {
val response = httpClient.get(urlString = "search/movie") {
parameter("query", movieName)
parameter("page", page)
}.body<MovieResultsDto>()

response.movies?.map { it.toDomain() }
}
)
response.movies?.map { it.toDomain() }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,19 @@ class SettingsRepositoryImpl(
}
}

override suspend fun getThemePreference(): Flow<Int> {
return dataStore.data.map { preferences ->
preferences[intPreferencesKey(KEY_THEME)] ?: 2
override suspend fun getThemePreference(): Result<Flow<Int>> {
return runCatching {
dataStore.data.map { preferences ->
preferences[intPreferencesKey(KEY_THEME)] ?: 2
}
}
}

override suspend fun getImageQualityPreference(): Flow<Int> {
return dataStore.data.map { preferences ->
preferences[intPreferencesKey(KEY_IMAGE_QUALITY)] ?: 1
override suspend fun getImageQualityPreference(): Result<Flow<Int>> {
return runCatching {
dataStore.data.map { preferences ->
preferences[intPreferencesKey(KEY_IMAGE_QUALITY)] ?: 1
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,14 @@ package com.vickbt.composeApp.data.network.utils
import com.vickbt.composeApp.data.mappers.toDomain
import com.vickbt.composeApp.data.network.models.ErrorResponseDto
import com.vickbt.composeApp.domain.models.ErrorResponse
import com.vickbt.composeApp.utils.ResultState
import io.ktor.client.call.body
import io.ktor.client.plugins.ClientRequestException
import io.ktor.client.plugins.RedirectResponseException
import io.ktor.client.plugins.ServerResponseException
import io.ktor.client.statement.HttpResponse
import io.ktor.util.network.UnresolvedAddressException
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf

suspend fun <T : Any?> safeApiCall(apiCall: suspend () -> T): ResultState<T> {
return try {
ResultState.Loading

ResultState.Success(apiCall.invoke())
} catch (e: RedirectResponseException) {
val error = parseNetworkError(e.response.body())
ResultState.Failure(exception = error)
} catch (e: ClientRequestException) {
val error = parseNetworkError(e.response.body())
ResultState.Failure(exception = error)
} catch (e: ServerResponseException) {
val error = parseNetworkError(e.response.body())
ResultState.Failure(exception = error)
} catch (e: UnresolvedAddressException) {
val error = parseNetworkError(exception = e)
ResultState.Failure(exception = error)
} catch (e: Exception) {
val error = parseNetworkError(exception = e)
ResultState.Failure(exception = error)
suspend fun <T : Any?> safeApiCall(apiCall: suspend () -> T): Result<Flow<T>> {
return runCatching {
flowOf(apiCall.invoke())
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ import kotlinx.coroutines.flow.Flow
interface FavoritesRepository {

/**Returns a list of movies that are favourite from the database*/
suspend fun getFavouriteMovies(): Flow<List<MovieDetails>>
suspend fun getFavouriteMovies(): Result<Flow<List<MovieDetails>?>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,31 @@ import com.vickbt.composeApp.domain.models.Cast
import com.vickbt.composeApp.domain.models.Movie
import com.vickbt.composeApp.domain.models.MovieDetails
import com.vickbt.composeApp.domain.utils.Constants.STARTING_PAGE_INDEX
import com.vickbt.composeApp.utils.ResultState
import kotlinx.coroutines.flow.Flow

interface MovieDetailsRepository {

/**Fetch movie details from network source*/
suspend fun fetchMovieDetails(movieId: Int): Flow<ResultState<MovieDetails>>
suspend fun fetchMovieDetails(movieId: Int): Result<Flow<MovieDetails?>>

/**Fetch movie cast from network source*/
suspend fun fetchMovieCast(movieId: Int): Flow<ResultState<Cast>>
suspend fun fetchMovieCast(movieId: Int): Result<Flow<Cast>>

/** Fetches similar movies from network source*/
suspend fun fetchSimilarMovies(
movieId: Int,
page: Int = STARTING_PAGE_INDEX
): Flow<ResultState<List<Movie>?>>
): Result<Flow<List<Movie>?>>

/**Save movie details to local cache*/
suspend fun saveFavoriteMovie(movie: MovieDetails)

/**Retrieve cached movie details from local cache based on its ID*/
suspend fun getFavoriteMovie(movieId: Int): MovieDetails?
suspend fun getFavoriteMovie(movieId: Int): Result<Flow<MovieDetails?>>

/**Delete previously saved movie details from local cache*/
suspend fun deleteFavoriteMovie(movieId: Int)

/**Check if movie details record is available in the local cache*/
suspend fun isMovieFavorite(movieId: Int): Flow<Boolean>
suspend fun isMovieFavorite(movieId: Int): Result<Flow<Boolean?>>
}
Loading

0 comments on commit 63504cd

Please sign in to comment.