Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migration to Result<> #126

Merged
merged 18 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading