Skip to content

Commit

Permalink
Merge pull request #2 from smish-hash/pagination
Browse files Browse the repository at this point in the history
Pagination3 support added
  • Loading branch information
smish-hash authored Jan 3, 2023
2 parents 8f76fde + c2abaf1 commit b78d327
Show file tree
Hide file tree
Showing 13 changed files with 242 additions and 149 deletions.
3 changes: 3 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0-rc01"
implementation "androidx.navigation:navigation-compose:2.5.0-rc01"
implementation 'androidx.paging:paging-compose:1.0.0-alpha17'
implementation("androidx.paging:paging-runtime:$paging_version")
// alternatively - without Android dependencies for tests
testImplementation("androidx.paging:paging-common:$paging_version")

// coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1'
Expand Down
4 changes: 3 additions & 1 deletion app/src/main/java/com/smish/abda/data/api/MovieApiService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ interface MovieApiService {
searchQuery: String,
@Query("page")
page: Int,
@Query("type")
type: String,
@Query("apikey")
apiKey: String = BuildConfig.MOVIE_API_KEY
): Response<Movie>
): Movie

@GET(".")
suspend fun getMovieDetails(
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/com/smish/abda/data/db/MovieDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,7 @@ interface MovieDao {

@Query("DELETE FROM movies WHERE imdbID = :imdbID")
suspend fun delete(imdbID: String)

@Query("DELETE FROM movies")
suspend fun deleteAll()
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.smish.abda.data.repository

import com.smish.abda.data.model.movie.Movie
import androidx.paging.PagingData
import com.smish.abda.data.model.movie.Search
import com.smish.abda.data.model.moviedetail.MovieDetail
import com.smish.abda.data.repository.datasource.MovieLocalDataSource
Expand All @@ -14,9 +14,9 @@ class MovieRepositoryImpl(
private val movieLocalDataSource: MovieLocalDataSource
): MovieRepository {

override suspend fun getMovies(searchQuery: String, page: Int): Resource<Movie> {
val response = movieRemoteDataSource.getMovies(searchQuery, page)
if (response.isSuccessful) {
override fun getMovies(searchQuery: String, type: String, page: Int): Flow<PagingData<Search>> {
return movieRemoteDataSource.getMovies(searchQuery, type, page)
/*if (response.isSuccessful) {
response.body()?.let { res ->
if (res.response == "True") {
return Resource.Success(res)
Expand All @@ -25,7 +25,7 @@ class MovieRepositoryImpl(
}
}
}
return Resource.Error(response.message() ?: "Unknown error occurred")
return Resource.Error(response.message() ?: "Unknown error occurred")*/
}

override suspend fun getMovieDetails(imdbId: String): Resource<MovieDetail> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.smish.abda.data.repository.datasource

import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.smish.abda.data.api.MovieApiService
import com.smish.abda.data.model.movie.Search
import okio.IOException
import retrofit2.HttpException

private const val OMDB_STARTING_PAGE_INDEX = 1

class MoviePagingSource(
private val query: String,
private val type: String,
private val movieApiService: MovieApiService
): PagingSource<Int, Search>() {

override fun getRefreshKey(state: PagingState<Int, Search>): Int? {
/* We need to get the previous key (or next key if previous is null) of the page
that was closest to the most recently accessed index.
Anchor position is the most recently accessed index.*/
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
}
}

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Search> {
val pageIndex = params.key ?: OMDB_STARTING_PAGE_INDEX

return try {
val response = movieApiService.getMovies(
searchQuery = query,
type = type,
page = pageIndex
)
// can return load state . error here

val movies: List<Search> = (response.movies ?: emptyList()) as List<Search>
val nextKey =
if (movies.isEmpty()) {
null
} else {
/* By default, initial load size = 3 * NETWORK PAGE SIZE
ensure we're not requesting duplicating items at the 2nd request
pageIndex + (params.loadSize / NETWORK_PAGE_SIZE)*/
pageIndex + 1
}
LoadResult.Page(
data = movies,
prevKey = if (pageIndex == OMDB_STARTING_PAGE_INDEX) null else pageIndex,
nextKey = nextKey
)
} catch (exception: IOException) {
return LoadResult.Error(exception)
} catch (exception: HttpException) {
return LoadResult.Error(exception)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.smish.abda.data.repository.datasource

import com.smish.abda.data.model.movie.Movie
import androidx.paging.PagingData
import com.smish.abda.data.model.movie.Search
import com.smish.abda.data.model.moviedetail.MovieDetail
import kotlinx.coroutines.flow.Flow
import retrofit2.Response

interface MovieRemoteDataSource {
// defining functions to communicate with the API
// use for both search and all
suspend fun getMovies(searchQuery: String, page: Int): Response<Movie>
fun getMovies(searchQuery: String, type: String, page: Int): Flow<PagingData<Search>>
suspend fun getMovieDetails(imdbId: String): Response<MovieDetail>
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
package com.smish.abda.data.repository.datasourceimpl

import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.smish.abda.data.api.MovieApiService
import com.smish.abda.data.model.movie.Movie
import com.smish.abda.data.model.movie.Search
import com.smish.abda.data.model.moviedetail.MovieDetail
import com.smish.abda.data.repository.datasource.MoviePagingSource
import com.smish.abda.data.repository.datasource.MovieRemoteDataSource
import kotlinx.coroutines.flow.Flow
import retrofit2.Response

class MovieRemoteDataSourceImpl(
private val movieAPI: MovieApiService
): MovieRemoteDataSource {

override suspend fun getMovies(searchQuery: String, page: Int): Response<Movie> {
return movieAPI.getMovies(searchQuery, page)
override fun getMovies(searchQuery: String, type: String, page: Int): Flow<PagingData<Search>> {
return Pager(
config = PagingConfig(
pageSize = 10,
enablePlaceholders = false
),
pagingSourceFactory = {
MoviePagingSource(query = searchQuery, type = type, movieApiService = movieAPI)
}
).flow
}

override suspend fun getMovieDetails(imdbId: String): Response<MovieDetail> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package com.smish.abda.domain.repository

import com.smish.abda.data.model.movie.Movie
import androidx.paging.PagingData
import com.smish.abda.data.model.movie.Search
import com.smish.abda.data.model.moviedetail.MovieDetail
import com.smish.abda.util.Resource
import kotlinx.coroutines.flow.Flow

interface MovieRepository {
// this will be used for both get all movies and search
suspend fun getMovies(searchQuery: String, page: Int): Resource<Movie>
fun getMovies(searchQuery: String, type: String, page: Int): Flow<PagingData<Search>>
suspend fun getMovieDetails(imdbId: String): Resource<MovieDetail>

suspend fun bookmarkMovie(movie: Search)
Expand Down
9 changes: 5 additions & 4 deletions app/src/main/java/com/smish/abda/domain/usecase/GetMovies.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package com.smish.abda.domain.usecase

import com.smish.abda.data.model.movie.Movie
import androidx.paging.PagingData
import com.smish.abda.data.model.movie.Search
import com.smish.abda.domain.repository.MovieRepository
import com.smish.abda.util.Resource
import kotlinx.coroutines.flow.Flow

class GetMovies(private val movieRepository: MovieRepository) {
suspend fun getMovies(searchQuery: String, page: Int): Resource<Movie> {
return movieRepository.getMovies(searchQuery, page)
fun getMovies(searchQuery: String, type: String, page: Int): Flow<PagingData<Search>> {
return movieRepository.getMovies(searchQuery, type, page)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.smish.abda.ui.movieList.components

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.items
import com.smish.abda.data.model.movie.Search

@Composable
fun MovieList(
movies: LazyPagingItems<Search>,
modifier: Modifier = Modifier,
onMovieClick: (Search) -> Unit,
onBookmarkClick: (isChecked: Boolean, movie: Search) -> Unit
) {
when (movies.loadState.refresh) {
LoadState.Loading -> {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
CircularProgressIndicator()
}
}
is LoadState.Error -> {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Something went wrong")
}
}
else -> {
/*LazyColumn(modifier = modifier) {
itemsIndexed(books) { index, item ->
item?.let {
BookItem(
book = item,
modifier = Modifier
.fillMaxWidth()
.background(getBackgroundForIndex(index))
.padding(vertical = 15.dp)
)
}
}
}*/
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
items(movies) { movie ->
if (movie != null) {
val (isChecked, setChecked) = remember { mutableStateOf(false) }
MovieListItem(
movie = movie,
onMovieClick = {
// call the movie detail api and trigger the bottom sheet
onMovieClick(movie)
},
isChecked,
onBookmarkClick = {
setChecked(!isChecked)
onBookmarkClick(isChecked, movie)
}
)
}
}
}
}
}
}
Loading

0 comments on commit b78d327

Please sign in to comment.