Skip to content

Commit

Permalink
Merge pull request #127 from VictorKabata/ktx-paging
Browse files Browse the repository at this point in the history
Added pagination
  • Loading branch information
VictorKabata authored Sep 25, 2024
2 parents 63504cd + 7c715e2 commit ec6cf96
Show file tree
Hide file tree
Showing 23 changed files with 258 additions and 141 deletions.
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

0 comments on commit ec6cf96

Please sign in to comment.