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

Added pagination #127

Merged
merged 10 commits into from
Sep 25, 2024
Merged
2 changes: 2 additions & 0 deletions composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ kotlin {

implementation(libs.room.runtime)
implementation(libs.sqlite.bundled)

implementation(libs.bundles.paging)
}

commonTest.dependencies {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package com.vickbt.composeApp.data.datasources

import androidx.paging.PagingData
import app.cash.paging.Pager
import app.cash.paging.PagingConfig
import com.vickbt.composeApp.data.cache.AppDatabase
import com.vickbt.composeApp.data.mappers.toDomain
import com.vickbt.composeApp.data.mappers.toEntity
import com.vickbt.composeApp.data.network.models.CastDto
import com.vickbt.composeApp.data.network.models.MovieDetailsDto
import com.vickbt.composeApp.data.network.models.MovieResultsDto
import com.vickbt.composeApp.data.network.utils.safeApiCall
import com.vickbt.composeApp.data.paging.BasePagingSource
import com.vickbt.composeApp.domain.models.Cast
import com.vickbt.composeApp.domain.models.Movie
import com.vickbt.composeApp.domain.models.MovieDetails
Expand All @@ -25,6 +29,8 @@ class MovieDetailsRepositoryImpl(
private val appDatabase: AppDatabase
) : MovieDetailsRepository {

private val pagingConfig = PagingConfig(pageSize = 20, enablePlaceholders = false)

override suspend fun fetchMovieDetails(movieId: Int): Result<Flow<MovieDetails?>> {
val isMovieCached = isMovieFavorite(movieId = movieId).getOrDefault(flowOf(false))
.firstOrNull()
Expand All @@ -33,8 +39,8 @@ class MovieDetailsRepositoryImpl(
getFavoriteMovie(movieId = movieId)
} else {
safeApiCall {
httpClient.get(urlString = "movie/$movieId").body<MovieDetailsDto>().toDomain()
}
httpClient.get(urlString = "movie/$movieId").body<MovieDetailsDto>().toDomain()
}
}
}

Expand All @@ -44,17 +50,21 @@ class MovieDetailsRepositoryImpl(
}
}

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

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

return runCatching {
Pager(
config = pagingConfig,
pagingSourceFactory = { pagingSource }
).flow
}
}

override suspend fun saveFavoriteMovie(movie: MovieDetails) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package com.vickbt.composeApp.data.datasources

import app.cash.paging.Pager
import app.cash.paging.PagingConfig
import app.cash.paging.PagingData
import com.vickbt.composeApp.data.mappers.toDomain
import com.vickbt.composeApp.data.network.models.MovieResultsDto
import com.vickbt.composeApp.data.network.utils.safeApiCall
import com.vickbt.composeApp.data.paging.BasePagingSource
import com.vickbt.composeApp.domain.models.Movie
import com.vickbt.composeApp.domain.repositories.MoviesRepository
import io.ktor.client.HttpClient
Expand All @@ -15,10 +19,12 @@ class MoviesRepositoryImpl(
private val httpClient: HttpClient
) : MoviesRepository {

override suspend fun fetchNowPlayingMovies(page: Int): Result<Flow<List<Movie>?>> {
private val pagingConfig = PagingConfig(pageSize = 20, enablePlaceholders = false)

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

response.movies?.map { it.toDomain() }
Expand All @@ -27,35 +33,55 @@ class MoviesRepositoryImpl(

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

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

return runCatching {
Pager(
config = pagingConfig,
pagingSourceFactory = { pagingSource }
).flow
}
}

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

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

return runCatching {
Pager(
config = pagingConfig,
pagingSourceFactory = { pagingSource }
).flow
}
}

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

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

return runCatching {
Pager(
config = pagingConfig,
pagingSourceFactory = { pagingSource }
).flow
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.vickbt.composeApp.data.datasources

import app.cash.paging.Pager
import app.cash.paging.PagingConfig
import app.cash.paging.PagingData
import com.vickbt.composeApp.data.mappers.toDomain
import com.vickbt.composeApp.data.network.models.MovieResultsDto
import com.vickbt.composeApp.data.network.utils.safeApiCall
import com.vickbt.composeApp.data.paging.BasePagingSource
import com.vickbt.composeApp.domain.models.Movie
import com.vickbt.composeApp.domain.repositories.SearchRepository
import io.ktor.client.HttpClient
Expand All @@ -15,17 +18,23 @@ class SearchRepositoryImpl(
private val httpClient: HttpClient
) : SearchRepository {

override suspend fun searchMovie(
movieName: String,
page: Int
): Result<Flow<List<Movie>?>> {
return safeApiCall {
private val pagingConfig = PagingConfig(pageSize = 20, enablePlaceholders = false)

override suspend fun searchMovie(movieName: String): Result<Flow<PagingData<Movie>>> {
val pagingSource = BasePagingSource { page ->
val response = httpClient.get(urlString = "search/movie") {
parameter("query", movieName)
parameter("page", page)
}.body<MovieResultsDto>()
}.body<MovieResultsDto>().movies

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

response.movies?.map { it.toDomain() }
return runCatching {
Pager(
config = pagingConfig,
pagingSourceFactory = { pagingSource }
).flow
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ data class MovieDetailsDto(
val adult: Boolean? = null,

@SerialName("backdrop_path")
val backdropPath: String,
val backdropPath: String? = null,

@SerialName("genres")
val genres: List<GenreDto>? = null,
Expand All @@ -37,7 +37,7 @@ data class MovieDetailsDto(
val popularity: Double? = null,

@SerialName("poster_path")
val posterPath: String,
val posterPath: String? = null,

@SerialName("release_date")
val releaseDate: String? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ data class MovieDto(
val adult: Boolean? = null,

@SerialName("backdrop_path")
val backdropPath: String,
val backdropPath: String? = null,

@SerialName("genre_ids")
val genreIds: List<Int>? = null,
Expand All @@ -21,22 +21,22 @@ data class MovieDto(
val originalLanguage: String? = null,

@SerialName("original_title")
val originalTitle: String,
val originalTitle: String? = null,

@SerialName("overview")
val overview: String,
val overview: String? = null,

@SerialName("popularity")
val popularity: Double? = null,

@SerialName("poster_path")
val posterPath: String,
val posterPath: String? = null,

@SerialName("release_date")
val releaseDate: String? = null,

@SerialName("title")
val title: String,
val title: String? = null,

@SerialName("video")
val video: Boolean? = null,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.vickbt.composeApp.data.paging

import app.cash.paging.PagingSource
import app.cash.paging.PagingState
import com.vickbt.composeApp.domain.utils.Constants.STARTING_PAGE_INDEX

class BasePagingSource<T : Any>(val fetchData: suspend (page: Int) -> List<T>?) :
PagingSource<Int, T>() {

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, T> {
val page = params.key ?: STARTING_PAGE_INDEX

val data = fetchData(page) ?: emptyList()

return try {
LoadResult.Page(
data = data,
prevKey = if (page == 1) null else page - 1,
nextKey = if (data.isEmpty()) null else page + 1
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}

override fun getRefreshKey(state: PagingState<Int, T>): Int? {
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,23 @@ package com.vickbt.composeApp.domain.models
data class Movie(
val adult: Boolean? = null,

val backdropPath: String,
val backdropPath: String? = null,

val id: Int,

val originalLanguage: String? = null,

val originalTitle: String,
val originalTitle: String? = null,

val overview: String,
val overview: String? = null,

val popularity: Double? = null,

val posterPath: String,
val posterPath: String? = null,

val releaseDate: String? = null,

val title: String,
val title: String? = null,

val video: Boolean? = null,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ data class MovieDetails(

val adult: Boolean? = null,

val backdropPath: String,
val backdropPath: String? = null,

val homepage: String? = null,

Expand All @@ -20,7 +20,7 @@ data class MovieDetails(

val popularity: Double? = null,

val posterPath: String,
val posterPath: String? = null,

val releaseDate: String? = null,

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.vickbt.composeApp.domain.repositories

import androidx.paging.PagingData
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 kotlinx.coroutines.flow.Flow

interface MovieDetailsRepository {
Expand All @@ -15,10 +15,7 @@ interface MovieDetailsRepository {
suspend fun fetchMovieCast(movieId: Int): Result<Flow<Cast>>

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

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