diff --git a/data/build.gradle.kts b/data/build.gradle.kts index c5fdad45..a5e3fb70 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -55,4 +55,13 @@ dependencies { implementation(libs.retrofit) implementation(libs.converter.gson) implementation(libs.retrofit2.kotlin.coroutines.adapter) + + // Hilt + implementation(libs.hilt.dagger.android) + implementation(libs.hilt.work) + ksp(libs.hilt.dagger.compiler) + ksp(libs.hilt.compiler) + + // WorkManager + implementation(libs.work.runtime.ktx) } diff --git a/data/src/main/AndroidManifest.xml b/data/src/main/AndroidManifest.xml index 568741e5..8c4c9826 100644 --- a/data/src/main/AndroidManifest.xml +++ b/data/src/main/AndroidManifest.xml @@ -1,2 +1,5 @@ - \ No newline at end of file + + + + \ No newline at end of file diff --git a/data/src/main/kotlin/com/aliasadi/data/mapper/MovieDataMapper.kt b/data/src/main/kotlin/com/aliasadi/data/mapper/MovieDataMapper.kt deleted file mode 100644 index aabaa9ae..00000000 --- a/data/src/main/kotlin/com/aliasadi/data/mapper/MovieDataMapper.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.aliasadi.data.mapper - -import com.aliasadi.data.entities.MovieDbData -import com.aliasadi.domain.entities.MovieEntity - -/** - * Created by Ali Asadi on 13/05/2020 - **/ - -fun MovieEntity.toDbData() = MovieDbData( - id = id, - image = image, - description = description, - title = title, - category = category, - backgroundUrl = backgroundUrl -) diff --git a/data/src/main/kotlin/com/aliasadi/data/repository/movie/MovieDataSource.kt b/data/src/main/kotlin/com/aliasadi/data/repository/movie/MovieDataSource.kt index 1e44cd2c..3eedc2f3 100644 --- a/data/src/main/kotlin/com/aliasadi/data/repository/movie/MovieDataSource.kt +++ b/data/src/main/kotlin/com/aliasadi/data/repository/movie/MovieDataSource.kt @@ -5,7 +5,7 @@ import com.aliasadi.data.entities.MovieData import com.aliasadi.data.entities.MovieDbData import com.aliasadi.data.entities.MovieRemoteKeyDbData import com.aliasadi.domain.entities.MovieEntity -import com.aliasadi.domain.util.Result +import kotlin.Result /** * Created by Ali Asadi on 13/05/2020 diff --git a/data/src/main/kotlin/com/aliasadi/data/repository/movie/MovieLocalDataSource.kt b/data/src/main/kotlin/com/aliasadi/data/repository/movie/MovieLocalDataSource.kt index 29973738..d250a8b1 100644 --- a/data/src/main/kotlin/com/aliasadi/data/repository/movie/MovieLocalDataSource.kt +++ b/data/src/main/kotlin/com/aliasadi/data/repository/movie/MovieLocalDataSource.kt @@ -10,7 +10,6 @@ import com.aliasadi.data.entities.toDbData import com.aliasadi.data.entities.toDomain import com.aliasadi.data.exception.DataNotAvailableException import com.aliasadi.domain.entities.MovieEntity -import com.aliasadi.domain.util.Result /** * Created by Ali Asadi on 13/05/2020 @@ -25,16 +24,16 @@ class MovieLocalDataSource( override suspend fun getMovies(): Result> { val movies = movieDao.getMovies() return if (movies.isNotEmpty()) { - Result.Success(movies.map { it.toDomain() }) + Result.success(movies.map { it.toDomain() }) } else { - Result.Error(DataNotAvailableException()) + Result.failure(DataNotAvailableException()) } } override suspend fun getMovie(movieId: Int): Result { return movieDao.getMovie(movieId)?.let { - Result.Success(it.toDomain()) - } ?: Result.Error(DataNotAvailableException()) + Result.success(it.toDomain()) + } ?: Result.failure(DataNotAvailableException()) } override suspend fun saveMovies(movies: List) { diff --git a/data/src/main/kotlin/com/aliasadi/data/repository/movie/MovieRemoteMediator.kt b/data/src/main/kotlin/com/aliasadi/data/repository/movie/MovieRemoteMediator.kt index 1a6d1319..f28bf313 100644 --- a/data/src/main/kotlin/com/aliasadi/data/repository/movie/MovieRemoteMediator.kt +++ b/data/src/main/kotlin/com/aliasadi/data/repository/movie/MovieRemoteMediator.kt @@ -7,7 +7,6 @@ import androidx.paging.PagingState import androidx.paging.RemoteMediator import com.aliasadi.data.entities.MovieDbData import com.aliasadi.data.entities.MovieRemoteKeyDbData -import com.aliasadi.domain.util.Result private const val MOVIE_STARTING_PAGE_INDEX = 1 @@ -27,37 +26,38 @@ class MovieRemoteMediator( LoadType.APPEND -> local.getLastRemoteKey()?.nextPage ?: return MediatorResult.Success(endOfPaginationReached = true) } - Log.d("XXX", "MovieRemoteMediator: load() called with: loadType = $loadType, page: $page, stateLastItem = ${state.isEmpty()}") + Log.d(LOG_TAG, "MovieRemoteMediator: load() called with: loadType = $loadType, page: $page, stateLastItem = ${state.isEmpty()}") // There was a lag in loading the first page; as a result, it jumps to the end of the pagination. if (state.isEmpty() && page == 2) return MediatorResult.Success(endOfPaginationReached = false) - when (val result = remote.getMovies(page, state.config.pageSize)) { - is Result.Success -> { - Log.d("XXX", "MovieRemoteMediator: get movies from remote") - if (loadType == LoadType.REFRESH) { - local.clearMovies() - local.clearRemoteKeys() - } - - val movies = result.data + val result = remote.getMovies(page, state.config.pageSize) + return if (result.isSuccess) { + Log.d(LOG_TAG, "MovieRemoteMediator: get movies from remote") + if (loadType == LoadType.REFRESH) { + local.clearMovies() + local.clearRemoteKeys() + } - val endOfPaginationReached = movies.isEmpty() + val movies = result.getOrNull() ?: emptyList() - val prevPage = if (page == MOVIE_STARTING_PAGE_INDEX) null else page - 1 - val nextPage = if (endOfPaginationReached) null else page + 1 + val endOfPaginationReached = movies.isEmpty() - val key = MovieRemoteKeyDbData(prevPage = prevPage, nextPage = nextPage) + val prevPage = if (page == MOVIE_STARTING_PAGE_INDEX) null else page - 1 + val nextPage = if (endOfPaginationReached) null else page + 1 - local.saveMovies(movies) - local.saveRemoteKey(key) + val key = MovieRemoteKeyDbData(prevPage = prevPage, nextPage = nextPage) - return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached) - } + local.saveMovies(movies) + local.saveRemoteKey(key) - is Result.Error -> { - return MediatorResult.Error(result.error) - } + MediatorResult.Success(endOfPaginationReached = endOfPaginationReached) + } else { + MediatorResult.Error(result.exceptionOrNull() ?: RuntimeException("Unknown error")) } } + + companion object { + private const val LOG_TAG = "MovieRemoteMediator" + } } diff --git a/data/src/main/kotlin/com/aliasadi/data/repository/movie/MovieRepositoryImpl.kt b/data/src/main/kotlin/com/aliasadi/data/repository/movie/MovieRepositoryImpl.kt index 309fde82..0d015760 100644 --- a/data/src/main/kotlin/com/aliasadi/data/repository/movie/MovieRepositoryImpl.kt +++ b/data/src/main/kotlin/com/aliasadi/data/repository/movie/MovieRepositoryImpl.kt @@ -9,12 +9,6 @@ import com.aliasadi.data.entities.toDomain import com.aliasadi.data.repository.movie.favorite.FavoriteMoviesDataSource import com.aliasadi.domain.entities.MovieEntity import com.aliasadi.domain.repository.MovieRepository -import com.aliasadi.domain.util.Result -import com.aliasadi.domain.util.Result.Error -import com.aliasadi.domain.util.Result.Success -import com.aliasadi.domain.util.map -import com.aliasadi.domain.util.onError -import com.aliasadi.domain.util.onSuccess import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -61,9 +55,11 @@ class MovieRepositoryImpl( } override suspend fun getMovie(movieId: Int): Result { - return when (val localResult = local.getMovie(movieId)) { - is Success -> localResult - is Error -> remote.getMovie(movieId).map { it.toDomain() } + val localResult = local.getMovie(movieId) + return if (localResult.isSuccess) { + localResult + } else { + remote.getMovie(movieId).map { it.toDomain() } } } @@ -74,7 +70,7 @@ class MovieRepositoryImpl( .onSuccess { localFavorite.addMovieToFavorite(movieId) } - .onError { + .onFailure { remote.getMovie(movieId).onSuccess { movie -> local.saveMovies(listOf(movie)) localFavorite.addMovieToFavorite(movieId) @@ -85,22 +81,22 @@ class MovieRepositoryImpl( override suspend fun removeMovieFromFavorite(movieId: Int) = localFavorite.removeMovieFromFavorite(movieId) override suspend fun sync(): Boolean { - return when (val result = local.getMovies()) { - is Error -> false - is Success -> { - val movieIds = result.data.map { it.id } - return updateLocalWithRemoteMovies(movieIds) - } + val result = local.getMovies() + return if (result.isFailure) { + false + } else { + val movieIds = result.getOrNull()?.map { it.id } ?: emptyList() + updateLocalWithRemoteMovies(movieIds) } } private suspend fun updateLocalWithRemoteMovies(movieIds: List): Boolean { - return when (val remoteResult = remote.getMovies(movieIds)) { - is Error -> false - is Success -> { - local.saveMovies(remoteResult.data) - true - } + val remoteResult = remote.getMovies(movieIds) + return if (remoteResult.isFailure) { + false + } else { + local.saveMovies(remoteResult.getOrNull() ?: emptyList()) + true } } } diff --git a/data/src/main/kotlin/com/aliasadi/data/repository/movie/SearchMoviePagingSource.kt b/data/src/main/kotlin/com/aliasadi/data/repository/movie/SearchMoviePagingSource.kt index 5bdff9b3..45b0f4b9 100644 --- a/data/src/main/kotlin/com/aliasadi/data/repository/movie/SearchMoviePagingSource.kt +++ b/data/src/main/kotlin/com/aliasadi/data/repository/movie/SearchMoviePagingSource.kt @@ -3,8 +3,6 @@ package com.aliasadi.data.repository.movie import androidx.paging.PagingSource import androidx.paging.PagingState import com.aliasadi.data.entities.MovieData -import com.aliasadi.domain.util.Result.Error -import com.aliasadi.domain.util.Result.Success private const val STARTING_PAGE_INDEX = 1 @@ -19,14 +17,17 @@ class SearchMoviePagingSource( override suspend fun load(params: LoadParams): LoadResult { val page = params.key ?: STARTING_PAGE_INDEX - return when (val result = remote.search(query, page, params.loadSize)) { - is Success -> LoadResult.Page( - data = result.data.distinctBy { movie -> movie.id }, + val result = remote.search(query, page, params.loadSize) + + return if (result.isSuccess) { + val movies = result.getOrNull() ?: emptyList() + LoadResult.Page( + data = movies.distinctBy { movie -> movie.id }, prevKey = if (page == STARTING_PAGE_INDEX) null else page - 1, - nextKey = if (result.data.isEmpty()) null else page + 1 + nextKey = if (movies.isEmpty()) null else page + 1 ) - - is Error -> LoadResult.Error(result.error) + } else { + LoadResult.Error(result.exceptionOrNull() ?: RuntimeException("Unknown error")) } } diff --git a/data/src/main/kotlin/com/aliasadi/data/repository/movie/favorite/FavoriteMoviesDataSource.kt b/data/src/main/kotlin/com/aliasadi/data/repository/movie/favorite/FavoriteMoviesDataSource.kt index 97294093..9d946fa9 100644 --- a/data/src/main/kotlin/com/aliasadi/data/repository/movie/favorite/FavoriteMoviesDataSource.kt +++ b/data/src/main/kotlin/com/aliasadi/data/repository/movie/favorite/FavoriteMoviesDataSource.kt @@ -2,7 +2,6 @@ package com.aliasadi.data.repository.movie.favorite import androidx.paging.PagingSource import com.aliasadi.data.entities.MovieDbData -import com.aliasadi.domain.util.Result /** * @author by Ali Asadi on 22/08/2022 diff --git a/data/src/main/kotlin/com/aliasadi/data/repository/movie/favorite/FavoriteMoviesLocalDataSource.kt b/data/src/main/kotlin/com/aliasadi/data/repository/movie/favorite/FavoriteMoviesLocalDataSource.kt index e4873432..68737bf1 100644 --- a/data/src/main/kotlin/com/aliasadi/data/repository/movie/favorite/FavoriteMoviesLocalDataSource.kt +++ b/data/src/main/kotlin/com/aliasadi/data/repository/movie/favorite/FavoriteMoviesLocalDataSource.kt @@ -5,7 +5,6 @@ import com.aliasadi.data.db.favoritemovies.FavoriteMovieDao import com.aliasadi.data.entities.FavoriteMovieDbData import com.aliasadi.data.entities.MovieDbData import com.aliasadi.data.exception.DataNotAvailableException -import com.aliasadi.domain.util.Result /** * @author by Ali Asadi on 22/08/2022 @@ -25,15 +24,15 @@ class FavoriteMoviesLocalDataSource( } override suspend fun checkFavoriteStatus(movieId: Int): Result { - return Result.Success(favoriteMovieDao.get(movieId) != null) + return Result.success(favoriteMovieDao.get(movieId) != null) } override suspend fun getFavoriteMovieIds(): Result> { val movieIds = favoriteMovieDao.getAll().map { it.movieId } return if (movieIds.isNotEmpty()) { - Result.Success(movieIds) + Result.success(movieIds) } else { - Result.Error(DataNotAvailableException()) + Result.failure(DataNotAvailableException()) } } } diff --git a/data/src/main/kotlin/com/aliasadi/data/util/SafeApiCall.kt b/data/src/main/kotlin/com/aliasadi/data/util/SafeApiCall.kt deleted file mode 100644 index 2dd67638..00000000 --- a/data/src/main/kotlin/com/aliasadi/data/util/SafeApiCall.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.aliasadi.data.util - -import com.aliasadi.domain.util.Result - -suspend fun safeApiCall(apiCall: suspend () -> T): Result = try { - Result.Success(apiCall.invoke()) -} catch (e: Exception) { - Result.Error(e) -} diff --git a/presentation/src/main/kotlin/com/aliasadi/clean/workers/SyncWork.kt b/data/src/main/kotlin/com/aliasadi/data/workers/SyncWork.kt similarity index 82% rename from presentation/src/main/kotlin/com/aliasadi/clean/workers/SyncWork.kt rename to data/src/main/kotlin/com/aliasadi/data/workers/SyncWork.kt index 50b24f6e..039a2db1 100644 --- a/presentation/src/main/kotlin/com/aliasadi/clean/workers/SyncWork.kt +++ b/data/src/main/kotlin/com/aliasadi/data/workers/SyncWork.kt @@ -1,4 +1,4 @@ -package com.aliasadi.clean.workers +package com.aliasadi.data.workers import android.content.Context import android.util.Log @@ -25,21 +25,23 @@ class SyncWork @AssistedInject constructor( override suspend fun doWork(): Result = withContext(dispatchers.io) { return@withContext if (movieRepository.sync()) { - Log.d("XXX", "SyncWork: doWork() called -> success") + Log.d(LOG_TAG, "SyncWork: doWork() called -> success") Result.success() } else { val lastAttempt = runAttemptCount >= SYNC_WORK_MAX_ATTEMPTS if (lastAttempt) { - Log.d("XXX", "SyncWork: doWork() called -> failure") + Log.d(LOG_TAG, "SyncWork: doWork() called -> failure") Result.failure() } else { - Log.d("XXX", "SyncWork: doWork() called -> retry") + Log.d(LOG_TAG, "SyncWork: doWork() called -> retry") Result.retry() } } } companion object { + const val LOG_TAG = "SyncWork" + fun getOneTimeWorkRequest() = OneTimeWorkRequestBuilder() .setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()) .build() diff --git a/data/src/mock/kotlin/com/aliasadi/data/repository/movie/MovieRemoteDataSource.kt b/data/src/mock/kotlin/com/aliasadi/data/repository/movie/MovieRemoteDataSource.kt index 63db787f..276ae19c 100644 --- a/data/src/mock/kotlin/com/aliasadi/data/repository/movie/MovieRemoteDataSource.kt +++ b/data/src/mock/kotlin/com/aliasadi/data/repository/movie/MovieRemoteDataSource.kt @@ -6,8 +6,8 @@ import com.aliasadi.data.entities.toDomain import com.aliasadi.data.exception.DataNotAvailableException import com.aliasadi.data.util.JsonLoader import com.aliasadi.domain.entities.MovieEntity -import com.aliasadi.domain.util.Result import kotlinx.coroutines.delay +import kotlin.Result /** * Created by Ali Asadi on 24/05/2024 @@ -24,37 +24,37 @@ class MovieRemoteDataSource( override suspend fun getMovies(page: Int, limit: Int): Result> { delay(DEFAULT_API_DELAY) return if (page == 2) { - Result.Success(emptyList()) + Result.success(emptyList()) } else { - Result.Success(JsonLoader.loadMovies()) + Result.success(JsonLoader.loadMovies()) } } override suspend fun getMovies(movieIds: List): Result> { delay(DEFAULT_API_DELAY) val movies = JsonLoader.loadMovies().filter { it.id in movieIds } - return Result.Success(movies) + return Result.success(movies) } override suspend fun getMovie(movieId: Int): Result { delay(DEFAULT_API_DELAY) val movie = JsonLoader.loadMovies().find { it.id == movieId } return if (movie != null) { - Result.Success(movie) + Result.success(movie) } else { - Result.Error(DataNotAvailableException()) + Result.failure(DataNotAvailableException()) } } override suspend fun search(query: String, page: Int, limit: Int): Result> { delay(DEFAULT_API_DELAY) return if (page == 2) { - Result.Success(emptyList()) + Result.success(emptyList()) } else { val filteredMovies = JsonLoader.loadMovies().filter { it.title.contains(query, ignoreCase = true) || it.description.contains(query, ignoreCase = true) } - Result.Success(filteredMovies) + Result.success(filteredMovies) } } } diff --git a/data/src/prod/kotlin/com/aliasadi/data/repository/movie/MovieRemoteDataSource.kt b/data/src/prod/kotlin/com/aliasadi/data/repository/movie/MovieRemoteDataSource.kt index c6f68f60..8e3bf487 100644 --- a/data/src/prod/kotlin/com/aliasadi/data/repository/movie/MovieRemoteDataSource.kt +++ b/data/src/prod/kotlin/com/aliasadi/data/repository/movie/MovieRemoteDataSource.kt @@ -2,8 +2,6 @@ package com.aliasadi.data.repository.movie import com.aliasadi.data.api.MovieApi import com.aliasadi.data.entities.MovieData -import com.aliasadi.data.util.safeApiCall -import com.aliasadi.domain.util.Result /** * Created by Ali Asadi on 13/05/2020 @@ -12,19 +10,19 @@ class MovieRemoteDataSource( private val movieApi: MovieApi ) : MovieDataSource.Remote { - override suspend fun getMovies(page: Int, limit: Int): Result> = safeApiCall { + override suspend fun getMovies(page: Int, limit: Int): Result> = runCatching { movieApi.getMovies(page, limit) } - override suspend fun getMovies(movieIds: List): Result> = safeApiCall { + override suspend fun getMovies(movieIds: List): Result> = runCatching { movieApi.getMovies(movieIds) } - override suspend fun getMovie(movieId: Int): Result = safeApiCall { + override suspend fun getMovie(movieId: Int): Result = runCatching { movieApi.getMovie(movieId) } - override suspend fun search(query: String, page: Int, limit: Int): Result> = safeApiCall { + override suspend fun search(query: String, page: Int, limit: Int): Result> = runCatching { movieApi.search(query, page, limit) } } diff --git a/data/src/test/kotlin/com/aliasadi/data/repository/movie/MovieLocalDataSourceTest.kt b/data/src/test/kotlin/com/aliasadi/data/repository/movie/MovieLocalDataSourceTest.kt index 81606216..258f0c6a 100644 --- a/data/src/test/kotlin/com/aliasadi/data/repository/movie/MovieLocalDataSourceTest.kt +++ b/data/src/test/kotlin/com/aliasadi/data/repository/movie/MovieLocalDataSourceTest.kt @@ -10,7 +10,6 @@ import com.aliasadi.data.entities.MovieRemoteKeyDbData import com.aliasadi.data.entities.toDbData import com.aliasadi.data.entities.toDomain import com.aliasadi.data.exception.DataNotAvailableException -import com.aliasadi.domain.util.Result import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertTrue import org.junit.Before @@ -49,9 +48,9 @@ class MovieLocalDataSourceTest : BaseTest() { val result = sut.getMovies() - assertTrue(result is Result.Success) - assertEquals(1, (result as Result.Success).data.size) - assertEquals(movieDbData.toDomain(), result.data[0]) + assertTrue(result.isSuccess) + assertEquals(1, result.getOrNull()?.size) + assertEquals(movieDbData.toDomain(), result.getOrNull()?.get(0)) } @Test @@ -60,8 +59,8 @@ class MovieLocalDataSourceTest : BaseTest() { val result = sut.getMovies() - assertTrue(result is Result.Error) - assertTrue((result as Result.Error).error is DataNotAvailableException) + assertTrue(result.isFailure) + assertTrue(result.exceptionOrNull() is DataNotAvailableException) } @Test @@ -71,8 +70,8 @@ class MovieLocalDataSourceTest : BaseTest() { val result = sut.getMovie(1) - assertTrue(result is Result.Success) - assertEquals(movieDbData.toDomain(), (result as Result.Success).data) + assertTrue(result.isSuccess) + assertEquals(movieDbData.toDomain(), result.getOrNull()) } @Test @@ -81,8 +80,8 @@ class MovieLocalDataSourceTest : BaseTest() { val result = sut.getMovie(1) - assertTrue(result is Result.Error) - assertTrue((result as Result.Error).error is DataNotAvailableException) + assertTrue(result.isFailure) + assertTrue(result.exceptionOrNull() is DataNotAvailableException) } @Test diff --git a/data/src/test/kotlin/com/aliasadi/data/repository/movie/MovieRemoteDataSourceTest.kt b/data/src/test/kotlin/com/aliasadi/data/repository/movie/MovieRemoteDataSourceTest.kt index 81e407af..e853aedb 100644 --- a/data/src/test/kotlin/com/aliasadi/data/repository/movie/MovieRemoteDataSourceTest.kt +++ b/data/src/test/kotlin/com/aliasadi/data/repository/movie/MovieRemoteDataSourceTest.kt @@ -3,8 +3,6 @@ package com.aliasadi.data.repository.movie import com.aliasadi.core.test.base.BaseTest import com.aliasadi.data.api.MovieApi import com.aliasadi.data.entities.MovieData -import com.aliasadi.domain.util.Result -import com.aliasadi.domain.util.asSuccessOrNull import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before @@ -31,9 +29,9 @@ class MovieRemoteDataSourceTest : BaseTest() { val result = sut.getMovies(1, 10) - assertTrue(result is Result.Success) - assertEquals(1, result.asSuccessOrNull()?.size) - assertEquals(movieDataList, result.asSuccessOrNull()) + assertTrue(result.isSuccess) + assertEquals(1, result.getOrNull()?.size) + assertEquals(movieDataList, result.getOrNull()) } @Test @@ -42,7 +40,7 @@ class MovieRemoteDataSourceTest : BaseTest() { val result = sut.getMovies(1, 10) - assertTrue(result is Result.Error) + assertTrue(result.isFailure) } @Test @@ -52,9 +50,9 @@ class MovieRemoteDataSourceTest : BaseTest() { val result = sut.getMovies(listOf(1, 2, 3)) - assertTrue(result is Result.Success) - assertEquals(1, (result as Result.Success).data.size) - assertEquals(movieData, result.data[0]) + assertTrue(result.isSuccess) + assertEquals(1, result.getOrNull()?.size) + assertEquals(movieData, result.getOrNull()?.get(0)) } @Test @@ -63,7 +61,7 @@ class MovieRemoteDataSourceTest : BaseTest() { val result = sut.getMovies(listOf(1, 2, 3)) - assertTrue(result is Result.Error) + assertTrue(result.isFailure) } @Test @@ -73,8 +71,8 @@ class MovieRemoteDataSourceTest : BaseTest() { val result = sut.getMovie(1) - assertTrue(result is Result.Success) - assertEquals(movieData, (result as Result.Success).data) + assertTrue(result.isSuccess) + assertEquals(movieData, result.getOrNull()) } @Test @@ -83,7 +81,7 @@ class MovieRemoteDataSourceTest : BaseTest() { val result = sut.getMovie(1) - assertTrue(result is Result.Error) + assertTrue(result.isFailure) } @Test @@ -93,9 +91,9 @@ class MovieRemoteDataSourceTest : BaseTest() { val result = sut.search("query", 1, 10) - assertTrue(result is Result.Success) - assertEquals(1, (result as Result.Success).data.size) - assertEquals(movieData, result.data[0]) + assertTrue(result.isSuccess) + assertEquals(1, result.getOrNull()?.size) + assertEquals(movieData, result.getOrNull()?.get(0)) } @Test @@ -104,6 +102,6 @@ class MovieRemoteDataSourceTest : BaseTest() { val result = sut.search("query", 1, 10) - assertTrue(result is Result.Error) + assertTrue(result.isFailure) } } diff --git a/data/src/test/kotlin/com/aliasadi/data/repository/movie/MovieRemoteMediatorTest.kt b/data/src/test/kotlin/com/aliasadi/data/repository/movie/MovieRemoteMediatorTest.kt index fb831838..8213f76c 100644 --- a/data/src/test/kotlin/com/aliasadi/data/repository/movie/MovieRemoteMediatorTest.kt +++ b/data/src/test/kotlin/com/aliasadi/data/repository/movie/MovieRemoteMediatorTest.kt @@ -11,7 +11,6 @@ import com.aliasadi.data.entities.MovieData import com.aliasadi.data.entities.MovieDbData import com.aliasadi.data.entities.MovieRemoteKeyDbData import com.aliasadi.data.exception.DataNotAvailableException -import com.aliasadi.domain.util.Result import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue @@ -24,6 +23,7 @@ import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import kotlin.Result @OptIn(ExperimentalPagingApi::class) class MovieRemoteMediatorTest : BaseTest() { @@ -50,7 +50,7 @@ class MovieRemoteMediatorTest : BaseTest() { @Test fun `load refresh success when remote returns data`() = runUnconfinedTest { val movieData = MovieData(1, "Title", "Description", "Image", "Category", "BackgroundUrl") - whenever(remote.getMovies(any(), any())).thenReturn(Result.Success(listOf(movieData))) + whenever(remote.getMovies(any(), any())).thenReturn(Result.success(listOf(movieData))) whenever(local.clearMovies()).thenReturn(Unit) whenever(local.clearRemoteKeys()).thenReturn(Unit) whenever(local.saveMovies(any())).thenReturn(Unit) @@ -76,7 +76,7 @@ class MovieRemoteMediatorTest : BaseTest() { @Test fun `load refresh error when remote returns error`() = runUnconfinedTest { val error = DataNotAvailableException() - whenever(remote.getMovies(any(), any())).thenReturn(Result.Error(error)) + whenever(remote.getMovies(any(), any())).thenReturn(Result.failure(error)) val pagingState = PagingState( pages = listOf(), @@ -110,7 +110,7 @@ class MovieRemoteMediatorTest : BaseTest() { fun `load append success when remote returns data`() = runUnconfinedTest { val movieData = MovieData(1, "Title", "Description", "Image", "Category", "BackgroundUrl") whenever(local.getLastRemoteKey()).thenReturn(MovieRemoteKeyDbData(1, null, 5)) - whenever(remote.getMovies(any(), any())).thenReturn(Result.Success(listOf(movieData))) + whenever(remote.getMovies(any(), any())).thenReturn(Result.success(listOf(movieData))) whenever(local.saveMovies(any())).thenReturn(Unit) whenever(local.saveRemoteKey(any())).thenReturn(Unit) @@ -133,7 +133,7 @@ class MovieRemoteMediatorTest : BaseTest() { fun `load append error when remote returns error`() = runUnconfinedTest { val error = DataNotAvailableException() whenever(local.getLastRemoteKey()).thenReturn(MovieRemoteKeyDbData(1, null, 4)) - whenever(remote.getMovies(any(), any())).thenReturn(Result.Error(error)) + whenever(remote.getMovies(any(), any())).thenReturn(Result.failure(error)) val pagingState = PagingState( pages = listOf(), @@ -152,7 +152,7 @@ class MovieRemoteMediatorTest : BaseTest() { fun `load append when there is remote key return end of page`() = runUnconfinedTest { val error = DataNotAvailableException() whenever(local.getLastRemoteKey()).thenReturn(null) - whenever(remote.getMovies(any(), any())).thenReturn(Result.Error(error)) + whenever(remote.getMovies(any(), any())).thenReturn(Result.failure(error)) val pagingState = PagingState( pages = listOf(), diff --git a/data/src/test/kotlin/com/aliasadi/data/repository/movie/MovieRepositoryImplTest.kt b/data/src/test/kotlin/com/aliasadi/data/repository/movie/MovieRepositoryImplTest.kt index f9dfe099..e4080f62 100644 --- a/data/src/test/kotlin/com/aliasadi/data/repository/movie/MovieRepositoryImplTest.kt +++ b/data/src/test/kotlin/com/aliasadi/data/repository/movie/MovieRepositoryImplTest.kt @@ -5,8 +5,6 @@ import com.aliasadi.data.entities.MovieData import com.aliasadi.data.entities.toDomain import com.aliasadi.data.repository.movie.favorite.FavoriteMoviesDataSource import com.aliasadi.domain.entities.MovieEntity -import com.aliasadi.domain.util.Result -import com.aliasadi.domain.util.asSuccessOrNull import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before @@ -33,12 +31,12 @@ class MovieRepositoryImplTest : BaseTest() { @Test fun `test getMovie returns movie from local if available`() = runUnconfinedTest { val movieEntity = MovieEntity(1, "Title", "Description", "Image", "Category", "BackgroundUrl") - whenever(local.getMovie(any())).thenReturn(Result.Success(movieEntity)) + whenever(local.getMovie(any())).thenReturn(Result.success(movieEntity)) val result = sut.getMovie(1) - assertTrue(result is Result.Success) - assertEquals(movieEntity, (result as Result.Success).data) + assertTrue(result.isSuccess) + assertEquals(movieEntity, result.getOrNull()) } @Test @@ -51,30 +49,30 @@ class MovieRepositoryImplTest : BaseTest() { title = "Category", category = "BackgroundUrl" ) - whenever(local.getMovie(any())).thenReturn(Result.Error(Exception())) - whenever(remote.getMovie(any())).thenReturn(Result.Success(movieData)) + whenever(local.getMovie(any())).thenReturn(Result.failure(Exception())) + whenever(remote.getMovie(any())).thenReturn(Result.success(movieData)) val result = sut.getMovie(1) - assertTrue(result is Result.Success) + assertTrue(result.isSuccess) - assertEquals(movieData.toDomain(), result.asSuccessOrNull()) + assertEquals(movieData.toDomain(), result.getOrNull()) } @Test fun `test checkFavoriteStatus returns correct status`() = runUnconfinedTest { - whenever(localFavorite.checkFavoriteStatus(any())).thenReturn(Result.Success(true)) + whenever(localFavorite.checkFavoriteStatus(any())).thenReturn(Result.success(true)) val result = sut.checkFavoriteStatus(1) - assertTrue(result is Result.Success) - assertEquals(true, (result as Result.Success).data) + assertTrue(result.isSuccess) + assertEquals(true, result.getOrNull()) } @Test fun `test addMovieToFavorite adds movie to favorites, success`() = runUnconfinedTest { val movieEntity = MovieEntity(1, "Title", "Description", "Image", "Category", "BackgroundUrl") - whenever(local.getMovie(any())).thenReturn(Result.Success(movieEntity)) + whenever(local.getMovie(any())).thenReturn(Result.success(movieEntity)) sut.addMovieToFavorite(1) @@ -86,8 +84,8 @@ class MovieRepositoryImplTest : BaseTest() { val movieData = MovieData(1, "Title", "Description", "Image", "Category", "BackgroundUrl") val exception = Exception() - whenever(local.getMovie(any())).thenReturn(Result.Error(exception)) - whenever(remote.getMovie(any())).thenReturn(Result.Success(movieData)) + whenever(local.getMovie(any())).thenReturn(Result.failure(exception)) + whenever(remote.getMovie(any())).thenReturn(Result.success(movieData)) sut.addMovieToFavorite(1) verify(remote).getMovie(1) @@ -106,8 +104,8 @@ class MovieRepositoryImplTest : BaseTest() { fun `test sync updates local with remote movies`() = runUnconfinedTest { val movieEntity = MovieEntity(1, "Title", "Description", "Image", "Category", "BackgroundUrl") val movieData = MovieData(1, "Title", "Description", "Image", "Category", "BackgroundUrl") - whenever(local.getMovies()).thenReturn(Result.Success(listOf(movieEntity))) - whenever(remote.getMovies(any>())).thenReturn(Result.Success(listOf(movieData))) + whenever(local.getMovies()).thenReturn(Result.success(listOf(movieEntity))) + whenever(remote.getMovies(any>())).thenReturn(Result.success(listOf(movieData))) val result = sut.sync() @@ -117,7 +115,7 @@ class MovieRepositoryImplTest : BaseTest() { @Test fun `test sync returns false when local getMovies fails`() = runUnconfinedTest { - whenever(local.getMovies()).thenReturn(Result.Error(Exception())) + whenever(local.getMovies()).thenReturn(Result.failure(Exception())) val result = sut.sync() @@ -127,8 +125,8 @@ class MovieRepositoryImplTest : BaseTest() { @Test fun `test sync returns false when remote getMovies fails`() = runUnconfinedTest { val movieEntity = MovieEntity(1, "Title", "Description", "Image", "Category", "BackgroundUrl") - whenever(local.getMovies()).thenReturn(Result.Success(listOf(movieEntity))) - whenever(remote.getMovies(any>())).thenReturn(Result.Error(Exception())) + whenever(local.getMovies()).thenReturn(Result.success(listOf(movieEntity))) + whenever(remote.getMovies(any>())).thenReturn(Result.failure(Exception())) val result = sut.sync() diff --git a/data/src/test/kotlin/com/aliasadi/data/repository/movie/SearchMoviePagingSourceTest.kt b/data/src/test/kotlin/com/aliasadi/data/repository/movie/SearchMoviePagingSourceTest.kt index 7ef0834b..a0b7bd44 100644 --- a/data/src/test/kotlin/com/aliasadi/data/repository/movie/SearchMoviePagingSourceTest.kt +++ b/data/src/test/kotlin/com/aliasadi/data/repository/movie/SearchMoviePagingSourceTest.kt @@ -5,7 +5,6 @@ import androidx.paging.PagingSource import androidx.paging.PagingState import com.aliasadi.core.test.base.BaseTest import com.aliasadi.data.entities.MovieData -import com.aliasadi.domain.util.Result import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Assert.assertTrue @@ -31,7 +30,7 @@ class SearchMoviePagingSourceTest : BaseTest() { val movieEntity2 = MovieData(2, "Title2", "Description2", "Image2", "Category2", "BackgroundUrl2") val duplicateMovieEntity = MovieData(1, "Title1", "Description1", "Image1", "Category1", "BackgroundUrl1") whenever(remote.search(any(), any(), any())).thenReturn( - Result.Success( + Result.success( listOf( movieEntity1, movieEntity2, @@ -53,7 +52,7 @@ class SearchMoviePagingSourceTest : BaseTest() { @Test fun `test load returns page on success with prevKey and nextKey`() = runUnconfinedTest { val movieData = MovieData(1, "Title", "Description", "Image", "Category", "BackgroundUrl") - whenever(remote.search(any(), any(), any())).thenReturn(Result.Success(listOf(movieData))) + whenever(remote.search(any(), any(), any())).thenReturn(Result.success(listOf(movieData))) val params = PagingSource.LoadParams.Append(key = 2, loadSize = 10, placeholdersEnabled = false) val result = sut.load(params) @@ -67,7 +66,7 @@ class SearchMoviePagingSourceTest : BaseTest() { @Test fun `test load returns page on success with end of pagination`() = runUnconfinedTest { - whenever(remote.search(any(), any(), any())).thenReturn(Result.Success(emptyList())) + whenever(remote.search(any(), any(), any())).thenReturn(Result.success(emptyList())) val params = PagingSource.LoadParams.Append(key = 2, loadSize = 10, placeholdersEnabled = false) val result = sut.load(params) @@ -82,7 +81,7 @@ class SearchMoviePagingSourceTest : BaseTest() { @Test fun `test load returns error on failure`() = runUnconfinedTest { val error = Exception("Network error") - whenever(remote.search(any(), any(), any())).thenReturn(Result.Error(error)) + whenever(remote.search(any(), any(), any())).thenReturn(Result.failure(error)) val params = PagingSource.LoadParams.Refresh(key = null, loadSize = 10, placeholdersEnabled = false) val result = sut.load(params) diff --git a/data/src/test/kotlin/com/aliasadi/data/repository/movie/favorite/FavoriteMoviesLocalDataSourceTest.kt b/data/src/test/kotlin/com/aliasadi/data/repository/movie/favorite/FavoriteMoviesLocalDataSourceTest.kt index caa24b28..3daaed1a 100644 --- a/data/src/test/kotlin/com/aliasadi/data/repository/movie/favorite/FavoriteMoviesLocalDataSourceTest.kt +++ b/data/src/test/kotlin/com/aliasadi/data/repository/movie/favorite/FavoriteMoviesLocalDataSourceTest.kt @@ -6,7 +6,6 @@ import com.aliasadi.data.db.favoritemovies.FavoriteMovieDao import com.aliasadi.data.entities.FavoriteMovieDbData import com.aliasadi.data.entities.MovieDbData import com.aliasadi.data.exception.DataNotAvailableException -import com.aliasadi.domain.util.Result import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before @@ -61,8 +60,8 @@ class FavoriteMoviesLocalDataSourceTest : BaseTest() { val result = sut.checkFavoriteStatus(movieId) - assertTrue(result is Result.Success) - assertEquals(true, (result as Result.Success).data) + assertTrue(result.isSuccess) + assertEquals(true, result.getOrNull()) } @Test @@ -72,8 +71,8 @@ class FavoriteMoviesLocalDataSourceTest : BaseTest() { val result = sut.checkFavoriteStatus(movieId) - assertTrue(result is Result.Success) - assertEquals(false, (result as Result.Success).data) + assertTrue(result.isSuccess) + assertEquals(false, result.getOrNull()) } @Test @@ -83,8 +82,8 @@ class FavoriteMoviesLocalDataSourceTest : BaseTest() { val result = sut.getFavoriteMovieIds() - assertTrue(result is Result.Success) - assertEquals(listOf(1, 2), (result as Result.Success).data) + assertTrue(result.isSuccess) + assertEquals(listOf(1, 2), result.getOrNull()) } @Test @@ -93,7 +92,7 @@ class FavoriteMoviesLocalDataSourceTest : BaseTest() { val result = sut.getFavoriteMovieIds() - assertTrue(result is Result.Error) - assertTrue((result as Result.Error).error is DataNotAvailableException) + assertTrue(result.isFailure) + assertTrue(result.exceptionOrNull() is DataNotAvailableException) } } diff --git a/domain/src/main/java/com/aliasadi/domain/repository/MovieRepository.kt b/domain/src/main/java/com/aliasadi/domain/repository/MovieRepository.kt index fa424f98..c8d67543 100644 --- a/domain/src/main/java/com/aliasadi/domain/repository/MovieRepository.kt +++ b/domain/src/main/java/com/aliasadi/domain/repository/MovieRepository.kt @@ -2,7 +2,6 @@ package com.aliasadi.domain.repository import androidx.paging.PagingData import com.aliasadi.domain.entities.MovieEntity -import com.aliasadi.domain.util.Result import kotlinx.coroutines.flow.Flow /** diff --git a/domain/src/main/java/com/aliasadi/domain/usecase/CheckFavoriteStatus.kt b/domain/src/main/java/com/aliasadi/domain/usecase/CheckFavoriteStatus.kt index 52be156f..f1f960e5 100644 --- a/domain/src/main/java/com/aliasadi/domain/usecase/CheckFavoriteStatus.kt +++ b/domain/src/main/java/com/aliasadi/domain/usecase/CheckFavoriteStatus.kt @@ -1,7 +1,7 @@ package com.aliasadi.domain.usecase import com.aliasadi.domain.repository.MovieRepository -import com.aliasadi.domain.util.Result +import kotlin.Result /** * @author by Ali Asadi on 13/08/2022 diff --git a/domain/src/main/java/com/aliasadi/domain/usecase/GetMovieDetails.kt b/domain/src/main/java/com/aliasadi/domain/usecase/GetMovieDetails.kt index 6eddedc9..3afc3a7b 100644 --- a/domain/src/main/java/com/aliasadi/domain/usecase/GetMovieDetails.kt +++ b/domain/src/main/java/com/aliasadi/domain/usecase/GetMovieDetails.kt @@ -2,7 +2,6 @@ package com.aliasadi.domain.usecase import com.aliasadi.domain.entities.MovieEntity import com.aliasadi.domain.repository.MovieRepository -import com.aliasadi.domain.util.Result /** * Created by Ali Asadi on 13/05/2020 diff --git a/domain/src/main/java/com/aliasadi/domain/util/Result.kt b/domain/src/main/java/com/aliasadi/domain/util/Result.kt deleted file mode 100644 index 3b101a3a..00000000 --- a/domain/src/main/java/com/aliasadi/domain/util/Result.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.aliasadi.domain.util - -import com.aliasadi.domain.util.Result.Error -import com.aliasadi.domain.util.Result.Success - -/** - * Created by Ali Asadi on 13/05/2020 - */ -sealed class Result { - data class Success(val data: T) : Result() - data class Error(val error: Throwable) : Result() -} - -inline fun Result.onSuccess( - block: (T) -> Unit -): Result = if (this is Success) also { block(data) } else this - -inline fun Result.onError( - block: (Throwable) -> Unit -): Result = if (this is Error) also { block(error) } else this - -fun Result.asSuccessOrNull(): T? = (this as? Success)?.data - -inline fun Result.map(transform: (T) -> A): Result { - return when (this) { - is Success -> Success(transform(data)) - is Error -> Error(error) - } -} \ No newline at end of file diff --git a/presentation/build.gradle.kts b/presentation/build.gradle.kts index 00830976..a2e303e3 100644 --- a/presentation/build.gradle.kts +++ b/presentation/build.gradle.kts @@ -99,9 +99,6 @@ dependencies { // Paging implementation(libs.paging.compose) - // WorkManager - implementation(libs.work.runtime.ktx) - // Compose implementation(platform(libs.compose.bom)) implementation(libs.compose.ui) diff --git a/presentation/src/main/kotlin/com/aliasadi/clean/App.kt b/presentation/src/main/kotlin/com/aliasadi/clean/App.kt index 533e3543..010447bd 100644 --- a/presentation/src/main/kotlin/com/aliasadi/clean/App.kt +++ b/presentation/src/main/kotlin/com/aliasadi/clean/App.kt @@ -11,7 +11,7 @@ import coil.disk.DiskCache import coil.memory.MemoryCache import coil.request.CachePolicy import coil.util.DebugLogger -import com.aliasadi.clean.workers.SyncWork +import com.aliasadi.data.workers.SyncWork import dagger.hilt.android.HiltAndroidApp import javax.inject.Inject diff --git a/presentation/src/main/kotlin/com/aliasadi/clean/ui/moviedetails/MovieDetailsViewModel.kt b/presentation/src/main/kotlin/com/aliasadi/clean/ui/moviedetails/MovieDetailsViewModel.kt index 2d47b7f2..75aa4418 100644 --- a/presentation/src/main/kotlin/com/aliasadi/clean/ui/moviedetails/MovieDetailsViewModel.kt +++ b/presentation/src/main/kotlin/com/aliasadi/clean/ui/moviedetails/MovieDetailsViewModel.kt @@ -7,9 +7,6 @@ import com.aliasadi.domain.usecase.AddMovieToFavorite import com.aliasadi.domain.usecase.CheckFavoriteStatus import com.aliasadi.domain.usecase.GetMovieDetails import com.aliasadi.domain.usecase.RemoveMovieFromFavorite -import com.aliasadi.domain.util.Result -import com.aliasadi.domain.util.asSuccessOrNull -import com.aliasadi.domain.util.onSuccess import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableStateFlow @@ -39,7 +36,7 @@ class MovieDetailsViewModel @Inject constructor( } private fun onInitialState() = launch { - val isFavorite = async { checkFavoriteStatus(movieId).asSuccessOrNull().orFalse() } + val isFavorite = async { checkFavoriteStatus(movieId).getOrNull().orFalse() } getMovieById(movieId).onSuccess { _uiState.value = MovieDetailsState( title = it.title, diff --git a/presentation/src/test/kotlin/com/aliasadi/clean/presentation/ui/moviedetails/MovieDetailsViewModelTest.kt b/presentation/src/test/kotlin/com/aliasadi/clean/presentation/ui/moviedetails/MovieDetailsViewModelTest.kt index 92c6bd4e..d2f82bba 100644 --- a/presentation/src/test/kotlin/com/aliasadi/clean/presentation/ui/moviedetails/MovieDetailsViewModelTest.kt +++ b/presentation/src/test/kotlin/com/aliasadi/clean/presentation/ui/moviedetails/MovieDetailsViewModelTest.kt @@ -10,7 +10,6 @@ import com.aliasadi.domain.usecase.AddMovieToFavorite import com.aliasadi.domain.usecase.CheckFavoriteStatus import com.aliasadi.domain.usecase.GetMovieDetails import com.aliasadi.domain.usecase.RemoveMovieFromFavorite -import com.aliasadi.domain.util.Result import com.google.common.truth.Truth.assertThat import org.junit.Test import org.mockito.Mockito.mock @@ -38,8 +37,8 @@ class MovieDetailsViewModelTest : BaseTest() { createViewModel( movieId = movieId, - movieDetailsResult = Result.Success(movie), - favoriteStatusResult = Result.Success(false) + movieDetailsResult = Result.success(movie), + favoriteStatusResult = Result.success(false) ) sut.uiState.test { @@ -59,8 +58,8 @@ class MovieDetailsViewModelTest : BaseTest() { val invalidMovieId = -1 createViewModel( movieId = invalidMovieId, - movieDetailsResult = Result.Error(mock()), - favoriteStatusResult = Result.Error(mock()) + movieDetailsResult = Result.failure(mock()), + favoriteStatusResult = Result.failure(mock()) ) sut.uiState.test { @@ -74,8 +73,8 @@ class MovieDetailsViewModelTest : BaseTest() { val movieId = 555 createViewModel( movieId = movieId, - movieDetailsResult = Result.Error(mock()), - favoriteStatusResult = Result.Success(false) + movieDetailsResult = Result.failure(mock()), + favoriteStatusResult = Result.success(false) ) sut.onFavoriteClicked() @@ -94,8 +93,8 @@ class MovieDetailsViewModelTest : BaseTest() { val movieId = 555 createViewModel( movieId = movieId, - movieDetailsResult = Result.Error(mock()), - favoriteStatusResult = Result.Success(true) + movieDetailsResult = Result.failure(mock()), + favoriteStatusResult = Result.success(true) ) sut.onFavoriteClicked() @@ -125,4 +124,4 @@ class MovieDetailsViewModelTest : BaseTest() { movieDetailsBundle = movieDetailsBundle ) } -} +} \ No newline at end of file