Skip to content
Open
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
9 changes: 9 additions & 0 deletions data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
5 changes: 4 additions & 1 deletion data/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>
17 changes: 0 additions & 17 deletions data/src/main/kotlin/com/aliasadi/data/mapper/MovieDataMapper.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -25,16 +24,16 @@ class MovieLocalDataSource(
override suspend fun getMovies(): Result<List<MovieEntity>> {
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<MovieEntity> {
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<MovieData>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -61,9 +55,11 @@ class MovieRepositoryImpl(
}

override suspend fun getMovie(movieId: Int): Result<MovieEntity> {
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() }
}
}

Expand All @@ -74,7 +70,7 @@ class MovieRepositoryImpl(
.onSuccess {
localFavorite.addMovieToFavorite(movieId)
}
.onError {
.onFailure {
remote.getMovie(movieId).onSuccess { movie ->
local.saveMovies(listOf(movie))
localFavorite.addMovieToFavorite(movieId)
Expand All @@ -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<Int>): 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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -19,14 +17,17 @@ class SearchMoviePagingSource(
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, MovieData> {
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"))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -25,15 +24,15 @@ class FavoriteMoviesLocalDataSource(
}

override suspend fun checkFavoriteStatus(movieId: Int): Result<Boolean> {
return Result.Success(favoriteMovieDao.get(movieId) != null)
return Result.success(favoriteMovieDao.get(movieId) != null)
}

override suspend fun getFavoriteMovieIds(): Result<List<Int>> {
val movieIds = favoriteMovieDao.getAll().map { it.movieId }
return if (movieIds.isNotEmpty()) {
Result.Success(movieIds)
Result.success(movieIds)
} else {
Result.Error(DataNotAvailableException())
Result.failure(DataNotAvailableException())
}
}
}
9 changes: 0 additions & 9 deletions data/src/main/kotlin/com/aliasadi/data/util/SafeApiCall.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.aliasadi.clean.workers
package com.aliasadi.data.workers

import android.content.Context
import android.util.Log
Expand All @@ -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<SyncWork>()
.setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -24,37 +24,37 @@ class MovieRemoteDataSource(
override suspend fun getMovies(page: Int, limit: Int): Result<List<MovieData>> {
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<Int>): Result<List<MovieData>> {
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<MovieData> {
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<List<MovieData>> {
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)
}
}
}
Loading