Skip to content

Commit b1cf9e6

Browse files
committed
init paging
1 parent ff6a6fc commit b1cf9e6

12 files changed

+283
-5
lines changed

app/build.gradle

+9
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@ dependencies {
6161

6262
implementation 'com.synnapps:carouselview:0.1.5'
6363

64+
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'
65+
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
66+
67+
//add paging library
68+
implementation 'android.arch.paging:runtime:1.0.1'
69+
70+
//add viewmodel and lifecycle
71+
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
72+
6473
testImplementation 'junit:junit:4.12'
6574
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
6675
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

app/src/main/java/com/arctouch/codechallenge/api/TmdbApi.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ interface TmdbApi {
4040
suspend fun getMoviesByName(
4141
@Query("query") title: String,
4242
@Query("api_key") apiKey: String = API_KEY,
43-
@Query("language") language: String = DEFAULT_LANGUAGE
43+
@Query("language") language: String = DEFAULT_LANGUAGE,
44+
@Query("page") page: Long
4445
): UpcomingMoviesResponse
4546
}

app/src/main/java/com/arctouch/codechallenge/di/TmbRepository.kt

+3-2
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@ class TmbRepositoryImpl(private val api: TmdbApi) : TmbRepository {
2727
override suspend fun getMoviesByName(
2828
title: String,
2929
apiKey: String,
30-
language: String
30+
language: String,
31+
page: Long
3132
): UpcomingMoviesResponse {
32-
return api.getMoviesByName(title)
33+
return api.getMoviesByName(title, page = page)
3334
}
3435

3536
}
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package com.arctouch.codechallenge.di
22

3+
import com.arctouch.codechallenge.ui.home.HomePagedViewModel
34
import com.arctouch.codechallenge.ui.home.HomeViewModel
45
import org.koin.dsl.module
56

67
val viewModelModule = module {
78
single { HomeViewModel(get()) }
9+
single { HomePagedViewModel(get()) }
810
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.arctouch.codechallenge.ui.home
2+
3+
import androidx.recyclerview.widget.DiffUtil
4+
import com.arctouch.codechallenge.model.Movie
5+
6+
class DiffUtilCallBack: DiffUtil.ItemCallback<Movie>() {
7+
override fun areItemsTheSame(oldItem: Movie, newItem: Movie): Boolean {
8+
return oldItem.id == newItem.id
9+
}
10+
11+
override fun areContentsTheSame(oldItem: Movie, newItem: Movie): Boolean {
12+
return oldItem == newItem
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.arctouch.codechallenge.ui.home
2+
3+
import android.app.SearchManager
4+
import android.content.Context
5+
import android.os.Bundle
6+
import android.view.*
7+
import androidx.appcompat.widget.SearchView
8+
import androidx.fragment.app.Fragment
9+
import androidx.lifecycle.LifecycleOwner
10+
import androidx.lifecycle.Observer
11+
import com.arctouch.codechallenge.R
12+
import kotlinx.android.synthetic.main.fragment_home.*
13+
import org.koin.android.viewmodel.ext.android.viewModel
14+
15+
class HomePagedFragment : Fragment() {
16+
17+
private val mViewModel: HomePagedViewModel by viewModel()
18+
private val homePagedListAdapter = HomePagedListAdapter()
19+
private lateinit var searchView: SearchView
20+
21+
override fun onCreateView(
22+
inflater: LayoutInflater,
23+
container: ViewGroup?,
24+
savedInstanceState: Bundle?
25+
): View? {
26+
return inflater.inflate(R.layout.fragment_home_paged, container, false)
27+
}
28+
29+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
30+
super.onViewCreated(view, savedInstanceState)
31+
setHasOptionsMenu(true)
32+
observeLiveData()
33+
initializeList()
34+
mViewModel.filterTextAll.value = ""
35+
}
36+
37+
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
38+
inflater.inflate(R.menu.search_menu, menu)
39+
val searchAction = menu.findItem(R.id.search_action)
40+
searchView = searchAction?.actionView as SearchView
41+
configSearchView()
42+
super.onCreateOptionsMenu(menu, inflater)
43+
}
44+
45+
private fun configSearchView() {
46+
val searchManager = activity?.getSystemService(Context.SEARCH_SERVICE) as SearchManager
47+
searchView.setSearchableInfo(searchManager.getSearchableInfo(activity?.componentName))
48+
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
49+
override fun onQueryTextChange(newText: String?): Boolean {
50+
mViewModel.filterTextAll.value = newText
51+
return true
52+
}
53+
54+
override fun onQueryTextSubmit(query: String?): Boolean {
55+
return true
56+
}
57+
})
58+
}
59+
60+
private fun observeLiveData() {
61+
mViewModel.getMovies().observe(this as LifecycleOwner, Observer {
62+
progressBar.visibility = View.GONE
63+
homePagedListAdapter.submitList(it)
64+
})
65+
}
66+
67+
private fun initializeList() {
68+
recyclerView.adapter = homePagedListAdapter
69+
}
70+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.arctouch.codechallenge.ui.home
2+
3+
import android.view.LayoutInflater
4+
import android.view.View
5+
import android.view.ViewGroup
6+
import androidx.paging.PagedListAdapter
7+
import androidx.recyclerview.widget.RecyclerView
8+
import com.arctouch.codechallenge.R
9+
import com.arctouch.codechallenge.model.Movie
10+
import com.arctouch.codechallenge.util.MovieImageUrlBuilder
11+
import com.bumptech.glide.Glide
12+
import com.bumptech.glide.request.RequestOptions
13+
import kotlinx.android.synthetic.main.movie_item.view.*
14+
15+
class HomePagedListAdapter(private val onClick: (Movie) -> Unit = {}) :
16+
PagedListAdapter<Movie, HomePagedListAdapter.ViewHolder>(DiffUtilCallBack()) {
17+
18+
class ViewHolder(itemView: View) :
19+
RecyclerView.ViewHolder(itemView) {
20+
21+
private val movieImageUrlBuilder = MovieImageUrlBuilder()
22+
23+
fun bind(movie: Movie) {
24+
itemView.titleTextView.text = movie.title
25+
itemView.genresTextView.text = movie.genres?.joinToString(separator = ", ") { it.name }
26+
itemView.releaseDateTextView.text = movie.releaseDate
27+
28+
Glide.with(itemView)
29+
.load(movie.posterPath?.let { movieImageUrlBuilder.buildPosterUrl(it) })
30+
.apply(RequestOptions().placeholder(R.drawable.ic_image_placeholder))
31+
.into(itemView.posterImageView)
32+
}
33+
}
34+
35+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
36+
val view = LayoutInflater.from(parent.context).inflate(R.layout.movie_item, parent, false)
37+
return ViewHolder(view)
38+
}
39+
40+
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
41+
val movie = getItem(position)
42+
movie?.let {
43+
holder.itemView.setOnClickListener { onClick(movie) }
44+
holder.bind(movie)
45+
}
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.arctouch.codechallenge.ui.home
2+
3+
import androidx.lifecycle.*
4+
import androidx.paging.DataSource
5+
import androidx.paging.LivePagedListBuilder
6+
import androidx.paging.PagedList
7+
import com.arctouch.codechallenge.di.TmbRepository
8+
import com.arctouch.codechallenge.model.Movie
9+
10+
11+
class HomePagedViewModel(private val repository: TmbRepository): ViewModel() {
12+
13+
lateinit var postLiveData: LiveData<PagedList<Movie>>
14+
var filterTextAll = MutableLiveData<String>()
15+
16+
init {
17+
initPaging()
18+
}
19+
20+
private fun initPaging() {
21+
val config = PagedList.Config.Builder()
22+
.setPageSize(20)
23+
.setEnablePlaceholders(false)
24+
.build()
25+
postLiveData = Transformations.switchMap(filterTextAll) { input ->
26+
initializedPagesListBuilder(config, input).build()
27+
}
28+
}
29+
30+
fun getMovies(): LiveData<PagedList<Movie>> = postLiveData
31+
32+
private fun initializedPagesListBuilder(config: PagedList.Config, input: String): LivePagedListBuilder<Int, Movie> {
33+
val dataSourceFactory = object : DataSource.Factory<Int, Movie>() {
34+
override fun create(): DataSource<Int, Movie> {
35+
return MoviesDataSource(repository, viewModelScope, input)
36+
}
37+
}
38+
return LivePagedListBuilder(dataSourceFactory, config)
39+
}
40+
}

app/src/main/java/com/arctouch/codechallenge/ui/home/HomeViewModel.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class HomeViewModel(private val repository: TmbRepository) : ViewModel(), KoinCo
3838

3939
fun searchMovies(query: String) {
4040
viewModelScope.launch {
41-
val upcomingMoviesResponse = repository.getMoviesByName(query)
41+
val upcomingMoviesResponse = repository.getMoviesByName(query, page = 1.toLong())
4242
val moviesWithGenres = addGenres(upcomingMoviesResponse)
4343
_searchLiveData.postValue(moviesWithGenres.toMutableList())
4444
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.arctouch.codechallenge.ui.home
2+
3+
import androidx.paging.PageKeyedDataSource
4+
import com.arctouch.codechallenge.data.Cache
5+
import com.arctouch.codechallenge.di.TmbRepository
6+
import com.arctouch.codechallenge.model.Movie
7+
import com.arctouch.codechallenge.model.UpcomingMoviesResponse
8+
import kotlinx.coroutines.CoroutineScope
9+
import kotlinx.coroutines.cancel
10+
import kotlinx.coroutines.launch
11+
12+
class MoviesDataSource(
13+
private val repository: TmbRepository,
14+
private val scope: CoroutineScope,
15+
private val input: String? = null
16+
) :
17+
PageKeyedDataSource<Int, Movie>() {
18+
override fun loadInitial(
19+
params: LoadInitialParams<Int>,
20+
callback: LoadInitialCallback<Int, Movie>
21+
) {
22+
scope.launch {
23+
// val upcomingMoviesResponse = repository.upcomingMovies(page = 1)
24+
val upcomingMoviesResponse = if (input.isNullOrEmpty()) {
25+
repository.upcomingMovies(page = 1)
26+
} else {
27+
repository.getMoviesByName(input, page = 1)
28+
}
29+
val moviesWithGenres = addGenres(upcomingMoviesResponse)
30+
callback.onResult(moviesWithGenres, null, 2)
31+
}
32+
}
33+
34+
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, Movie>) {
35+
scope.launch {
36+
val page = params.key
37+
// val upcomingMoviesResponse = repository.upcomingMovies(page = page.toLong())
38+
val upcomingMoviesResponse = if (input.isNullOrEmpty()) {
39+
repository.upcomingMovies(page = page.toLong())
40+
} else {
41+
repository.getMoviesByName(input, page = page.toLong())
42+
}
43+
val moviesWithGenres = addGenres(upcomingMoviesResponse)
44+
callback.onResult(moviesWithGenres, page.plus(1))
45+
}
46+
}
47+
48+
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, Movie>) {
49+
TODO("Not yet implemented")
50+
}
51+
52+
override fun invalidate() {
53+
super.invalidate()
54+
scope.cancel()
55+
}
56+
57+
private fun addGenres(upcomingMoviesResponse: UpcomingMoviesResponse): List<Movie> {
58+
return upcomingMoviesResponse.results.map { movie ->
59+
movie.copy(genres = Cache.genres.filter { movie.genreIds?.contains(it.id) == true })
60+
}
61+
}
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto"
4+
xmlns:tools="http://schemas.android.com/tools"
5+
android:layout_width="match_parent"
6+
android:layout_height="match_parent"
7+
tools:context=".ui.home.HomePagedFragment">
8+
9+
<androidx.recyclerview.widget.RecyclerView
10+
android:id="@+id/recyclerView"
11+
android:layout_width="match_parent"
12+
android:layout_height="match_parent"
13+
android:clipToPadding="false"
14+
android:paddingTop="12dp"
15+
android:paddingBottom="12dp"
16+
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
17+
tools:itemCount="10"
18+
tools:listitem="@layout/movie_item" />
19+
20+
<ProgressBar
21+
android:id="@+id/progressBar"
22+
android:layout_width="wrap_content"
23+
android:layout_height="wrap_content"
24+
android:layout_centerInParent="true"
25+
tools:visibility="gone" />
26+
27+
</RelativeLayout>

app/src/main/res/navigation/nav_graph.xml

+6-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
xmlns:app="http://schemas.android.com/apk/res-auto"
44
xmlns:tools="http://schemas.android.com/tools"
55
android:id="@+id/nav_graph.xml"
6-
app:startDestination="@id/homeFragment">
6+
app:startDestination="@id/homePagedFragment">
77

88
<fragment
99
android:id="@+id/homeFragment"
@@ -23,4 +23,9 @@
2323
android:name="movie"
2424
app:argType="com.arctouch.codechallenge.model.Movie" />
2525
</fragment>
26+
<fragment
27+
android:id="@+id/homePagedFragment"
28+
android:name="com.arctouch.codechallenge.ui.home.HomePagedFragment"
29+
android:label="fragment_home_paged"
30+
tools:layout="@layout/fragment_home_paged" />
2631
</navigation>

0 commit comments

Comments
 (0)