Android Paging Library Doesn't Scroll Up Correctly - android

I'm using the JetPack paging library with a network call (no database).
I am able to scroll down smoothly and load new pages of data, BUT, when scrolling up it stutters and quickly jumps to the top of the list. I am unable to scroll up smoothly.
Here is a video showing the problem: https://imgur.com/a/bRoelyF
What I've Tried:
Enabling retrofit caching
Using a LinearLayoutManager instead of GridLayoutManager
Following old and newer tutorials with versions 1.0.1 and 2.1.2 of the library
Here is my code:
MovieDataSource.kt:
private val movieDbApi: TheMovieDbApi
) : PageKeyedDataSource<Int, Movie>() {
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, Movie>) {}
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, Movie>) {
movieDbApi.getTopRatedMovies(BuildConfig.MOVIE_DATA_BASE_API, FIRST_PAGE).subscribe(
{
it?.let { callback.onResult(it.results, null, FIRST_PAGE + 1) }
}, {}
)
}
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, Movie>) {
movieDbApi.getTopRatedMovies(BuildConfig.MOVIE_DATA_BASE_API, params.key).subscribe(
{
val key = params.key + 1
it?.let {callback.onResult(it.results, key)
}
},{}
)
}
MovieDataSourceFactory.kt:
class MovieDataSourceFactory(private val movieDbApi: TheMovieDbApi) :
DataSource.Factory<Int, Movie>() {
// Is this where the MovieDataSource callBacks are sent?
val movieLiveDataSource = MutableLiveData<MovieDataSource>()
override fun create(): DataSource<Int, Movie> {
val movieDataSource = MovieDataSource(movieDbApi)
movieLiveDataSource.postValue(movieDataSource)
return movieDataSource
}
}
HomeViewModel.kt:
class HomeViewModel #Inject constructor(
theMovieDbApi: TheMovieDbApi
) : DisposingViewModel() {
var moviePagedList: LiveData<PagedList<Movie>>
private var liveDataSource: LiveData<MovieDataSource>
init {
val movieDataSourceFactory = MovieDataSourceFactory(theMovieDbApi)
liveDataSource = movieDataSourceFactory.movieLiveDataSource
val config = PagedList.Config.Builder()
.setEnablePlaceholders(true)
.setPageSize(MovieDataSource.PAGE_SIZE)
.build()
moviePagedList = LivePagedListBuilder(movieDataSourceFactory, config)
.build()
}
}
HomeViewModel.kt:
class HomeActivity : AppCompatActivity() {
#Inject
internal lateinit var viewModelFactory: ViewModelFactory<HomeViewModel>
private lateinit var viewModel: HomeViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
AndroidInjection.inject(this)
val adapter = HomeAdapter()
movie_recycler_view.setHasFixedSize(false)
movie_recycler_view.layoutManager = LinearLayoutManager(this)
val viewModel = ViewModelProvider(this, viewModelFactory).get(HomeViewModel::class.java)
viewModel.moviePagedList.observe(this, Observer {
adapter.submitList(it)
})
movie_recycler_view.adapter = adapter
}
}
HomeAdapter.kt:
class HomeAdapter : PagedListAdapter<Movie, HomeAdapter.MovieViewHolder>(USER_COMPARATOR) {
override fun getItemCount(): Int {
return super.getItemCount()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_movie, parent, false)
return MovieViewHolder(view)
}
override fun onBindViewHolder(holder: MovieViewHolder, position: Int) {
val movie = getItem(position)
movie?.let { holder.bind(it) }
}
class MovieViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind(movie: Movie) {
Picasso.get().load(BASE_IMAGE_URL + movie.poster_path).into(itemView.movie_image)
}
}
companion object {
private val USER_COMPARATOR = object : DiffUtil.ItemCallback<Movie>() {
override fun areItemsTheSame(oldItem: Movie, newItem: Movie): Boolean =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Movie, newItem: Movie): Boolean =
oldItem == newItem
}
}
}
If anyone has a solution or spots a problem I'd love to hear it!

I solved the problem.
It's because I didn't add a placeholder image to Picasso in the adapter.
Before:
Picasso.get()
.load(BASE_IMAGE_URL + movie.poster_path)
.into(itemView.movie_image)
After:
Picasso.get()
.load(BASE_IMAGE_URL + movie.poster_path)
.placeholder(R.drawable.placeholder)
.into(itemView.movie_image)
Now it loads well.
Another consideration is the size of the image, it takes a while to load a larger image especially if you are loading many of them within an infinite scroll.

Related

If data in a recycler view item is null then don't display the view holder

I have an API which give me the list of doctors. On it's last page only 1 item is there and other items are null like this:
After this i have used paging library for pagination
my pagingSource code: `
class DocPagingSource(val docRepository: DocRepository): PagingSource<Int, Data>() {
override fun getRefreshKey(state: PagingState<Int, Data>): Int? {
return state.anchorPosition?.let {
state.closestPageToPosition(it)?.prevKey?.plus(1)
?: state.closestPageToPosition(it)?.nextKey?.minus(1)
}
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Data> {
return try {
val currentPage = params.key?: 1
val city: String = ""
val response = docRepository.getDoctors(city, currentPage)
val page = Math.ceil(response.body()!!.total.toDouble()/5).toInt()
val data = response.body()!!.data
val responseData = mutableListOf<Data>()
responseData.addAll(data)
LoadResult.Page(
data = responseData,
prevKey = if(currentPage==1) null else -1,
nextKey = if (currentPage== page) null else currentPage.plus(1)
)
}catch (e: HttpException){
LoadResult.Error(e)
}catch (e: Exception){
LoadResult.Error(e)
}
}
`
My paging Adapter Code:
class DocAdapter(val context: Context): PagingDataAdapter<Data, DocAdapter.DocViewHolder>(DiffUtil()) {
private lateinit var binding: ItemDoctorsBinding
inner class DocViewHolder : RecyclerView.ViewHolder(binding.root) {
fun bind(item: Data?) {
binding.apply {
txtDocCity.text = item?.city
txtDocName.text = item?.docName
txtDocFees.text = item?.docConsultationFee
txtDocYOE.text = item?.docYoE
txtDocSpecialisation.text = item?.docSpecialisation
Glide.with(context)
.load(item?.docProfileImgUrl)
.fitCenter()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(docPhoto)
}
}
}
override fun onBindViewHolder(holder: DocViewHolder, position: Int) {
val item = getItem(position)
if (item!=null){
holder.bind(getItem(position)!!)
holder.setIsRecyclable(false)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DocViewHolder {
val inflater = LayoutInflater.from(context)
binding = ItemDoctorsBinding.inflate(inflater, parent, false)
return DocViewHolder()
}
class DiffUtil: androidx.recyclerview.widget.DiffUtil.ItemCallback<Data>(){
override fun areItemsTheSame(oldItem: Data, newItem: Data): Boolean {
return oldItem.docId == newItem.docId
}
override fun areContentsTheSame(oldItem: Data, newItem: Data): Boolean {
return oldItem==newItem
}
}}
what I am getting after reaching my 16th item in doctor list on last page it should show entry till 16th item but after that it also shows again like this:
Also if i dont use holder.setIsRecyclable(false) in pagingAdapter then this android icon not shown but then list is populated with previous doctors:
on the top DR. Sixteen is shown like this:
and in between it again shows like this:
My doctorViewModel Class:
class DocViewModel(val repository: DocRepository): ViewModel() {
val loading = MutableLiveData<Boolean>()
val docList = Pager(PagingConfig(5, maxSize = 100)){
DocPagingSource(repository)
}.flow.cachedIn(viewModelScope)}
My main Activity:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var docViewModel: DocViewModel
private lateinit var docAdapter: DocAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val docListRepository = DocRepository()
val docFactory = DocViewModelFactory(docListRepository)
docViewModel = ViewModelProvider(this, docFactory).get(DocViewModel::class.java)
docAdapter = DocAdapter(this)
lifecycleScope.launchWhenCreated {
docViewModel.docList.collect{
docAdapter.submitData(it)
}
}
binding.docRecyclerView.apply {
layoutManager = LinearLayoutManager(this#MainActivity)
adapter = docAdapter
setHasFixedSize(true)
}
}}
I have solved this error by applying a condition in my paging source code

notifyDataSetChanged() is not working after fetching data with Retrofit and DisposableSingleObserver

I've been trying to solve this problem for over 3 hours. Everything seems just fine on the Logcat and Debug mode. I'm fetching the List without any problem, Fragment is reading the MutableLiveData successfully. Only the notifyDataSetChanged() function is not working and also it doesn't give any error etc. If I send an ArrayList manually then it works but if it goes inside Retrofit and DisposableSingleObserver then even the manual list doesn't work.
I have tried every way that I could have found on the internet. I've looked for more than 20 different solution none of them have worked.
API - HoroscopeAPI.kt
interface HoroscopeAPI {
#GET("astraios/horoscopeList.json")
fun getHoroscope(): Single<List<Horoscope>>
}
Service - HoroscopeAPIService.kt
class HoroscopeAPIService {
private val BASE_URL = "https://wiuma.co"
private val api = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
.create(HoroscopeAPI::class.java)
fun getData(): Single<List<Horoscope>> {
return api.getHoroscope()
}
}
ViewModel - HoroscopeViewModel.kt
class HoroscopeViewModel : ViewModel() {
private val horoscopeApiService = HoroscopeAPIService()
private val disposable = CompositeDisposable()
val horoscopeList = MutableLiveData<List<Horoscope>>()
fun getDataFromAPI() {
loadingStatus.value = true
disposable.add(
horoscopeApiService.getData()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableSingleObserver<List<Horoscope>>() {
override fun onSuccess(t: List<Horoscope>) {
horoscopeList.value = t
}
override fun onError(e: Throwable) {
e.printStackTrace()
}
})
)
}
}
Fragment - Horoscope.kt
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
horoscopeViewModel =
ViewModelProvider(this).get(HoroscopeViewModel::class.java)
val root = inflater.inflate(R.layout.fragment_horoscope, container, false)
horoscopeViewModel.getDataFromAPI()
horoscopeRecyclerView = root.findViewById(R.id.horoscopeRecyclerView)
horoscopeRecyclerView.layoutManager = LinearLayoutManager(context)
horoscopeRecyclerView.adapter = recyclerViewAdapter
observeData()
return root
}
fun observeData() {
horoscopeViewModel.horoscopeList.observe(viewLifecycleOwner, Observer { horoscope ->
horoscope?.let {
recyclerViewAdapter.updateList(horoscope)
}
})}
**Adapter - HoroscopeRecyclerAdapter.kt **
class HoroscopeRecyclerAdapter(val horoscopeList: ArrayList<Horoscope>) :
RecyclerView.Adapter<HoroscopeRecyclerAdapter.HoroscopeViewHolder>() {
class HoroscopeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HoroscopeViewHolder {
val inflater = LayoutInflater.from(parent.context)
val view = inflater.inflate(R.layout.horoscope_recycler_row, parent, false)
return HoroscopeViewHolder(view)
}
#SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: HoroscopeViewHolder, position: Int) {
holder.itemView.horoscopeName.text = horoscopeList.get(position).nameHoroscope
holder.itemView.horoscopeDates.text =
horoscopeList.get(position).startDate + " " + horoscopeList.get(position).endDate
//TODO Gorsel baglantisi eklenecek.
holder.itemView.setOnClickListener {
val action =
HoroscopeFragmentDirections.actionNavigationHoroscopeToNavigationHoroscopeDetails(0)
Navigation.findNavController(it).navigate(action)
}
}
override fun getItemCount(): Int {
return horoscopeList.size
}
fun updateList(newHoroscopeList: List<Horoscope>) {
horoscopeList.clear()
horoscopeList.addAll(newHoroscopeList)
notifyDataSetChanged()
}}
I ran your project from Github. notifyDataSetChanged() seems to be working fine. The reason why the list items aren't showing up is that the visibility of the RecyclerView is set to GONE. It needs to be set back to VISIBLE when the results arrive:
fun observeData() {
horoscopeViewModel.horoscopeList.observe(viewLifecycleOwner, Observer { horoscope ->
horoscope?.let {
errorText.visibility = View.GONE
progressBar.visibility = View.GONE
horoscopeRecyclerView.visibility = View.VISIBLE
recyclerViewAdapter.updateList(it)
}
})

How to Update item values without changing the whole List?

I'm working on a Grid RecyclerView for Scores App. Scores will be updated every 5 secs from API Call...
I'm using this library for my Recyclerview Adapter.
I'm using Observable Fields & List.
I need to update only the scores (textviews) but now the RecyclerView is getting updated every 5 secs.
Please Guide me in right direction. Thank you!
Full Code in Gist
class DashboardFragment : Fragment() {
private lateinit var dashboardViewModel: DashboardViewModel
private lateinit var binding: FragmentDashboardBinding
lateinit var retrofit: Retrofit
lateinit var apiService: APIService
lateinit var disposable: Disposable
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
dashboardViewModel = ViewModelProvider(this).get(DashboardViewModel::class.java)
fetchData()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil
.inflate(inflater, R.layout.fragment_dashboard, container, false)
binding.viewModel = dashboardViewModel
return binding.root
}
fun fetchData() {
val interceptor = HttpLoggingInterceptor()
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
val client = OkHttpClient.Builder()
.addInterceptor(interceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build()
val gson = GsonBuilder()
.setLenient()
.create()
retrofit = Retrofit.Builder()
.baseUrl(APIService.BASE_URL)
.client(client)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
apiService = this.retrofit.create(APIService::class.java)
callIndicesEndpoint(null)
disposable = Observable.interval(1000, 2000, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ aLong: Long? -> this.refreshIndices(aLong)})
{ throwable: Throwable -> this.onError(throwable) }
}
#SuppressLint("CheckResult")
private fun callIndicesEndpoint(aLong: Long?) {
val observable =
apiService.indices
observable.subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread())
.map { result: ObservableArrayList<Indices> -> result }
.subscribe(
{ data: ObservableArrayList<Indices> ->
this.handleResults(data)
}
) { t: Throwable ->
this.handleError(t)
}
}
#SuppressLint("CheckResult")
private fun refreshIndices(aLong: Long?) {
val observable =
apiService.indices
observable.subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread())
.map { result: ObservableArrayList<Indices> -> result }
.subscribe({ data: ObservableArrayList<Indices> -> this.refreshResults(data)})
{ t: Throwable ->this.handleError(t)}
}
private fun handleResults(data: ObservableArrayList<Indices>) {
dashboardViewModel.populate(data)
}
private fun refreshResults(data: ObservableArrayList<Indices>) {
dashboardViewModel.refresh(data)
}
private fun onError(throwable: Throwable) {
Log.e(">>>", "ERROR")
}
private fun handleError(t: Throwable) {
Log.e("> >", t.localizedMessage + " - Err - " + t.cause)
//Add your error here.
}
override fun onPause() {
super.onPause()
disposable.dispose()
}
}
VIEWMODEL
class DashboardViewModel : ViewModel() {
val indices: ObservableList<Indices> = ObservableArrayList<Indices>()
fun populate(data: ArrayList<Indices>) {
indices.clear()
indices.addAll(data)
}
fun refresh(data: ArrayList<Indices>) {
// How to refresh only Items without recreating the List
}
val itemIds: ItemIds<Any> =
ItemIds { position, item -> position.toLong() }
val indicesItem: ItemBinding<Indices> =
ItemBinding.of<Indices>(BR.item, R.layout.item_futures)
}
You can use broadcast receiver functionality for update particular items from recyclerview .
I think what you need is to use DiffUtil, it'll be of great help as
DiffUtil figures out what has changed, RecyclerView can use that information to update only the items that were changed, added, removed, or moved, which is much more efficient than redoing the entire list.
Here is a course you can use to get a hand of it: https://codelabs.developers.google.com/codelabs/kotlin-android-training-diffutil-databinding/#3
When I had something like that I used the DiffUtil.
I had to update rates every X seconds and not change the whole list.
So the adapter should be something like this:
class RatesAdapter :
ListAdapter<Rate, RecyclerView.ViewHolder>(RateDiffCallback()) {
private val baseRateView = 0
private val rateView = 1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == rateView) {
RateHolder(
RateItemBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
)
} else {
BaseRateHolder(
BaseRateLayoutBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
)
}
}
override fun getItemViewType(position: Int): Int {
return if (position == 0) {
baseRateView
} else rateView
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val rate = getItem(position)
if (holder.itemViewType == rateView) {
(holder as RateHolder).bind(rate)
holder.itemView.setOnClickListener {
swapper.itemSwap(rate)
}
} else {
(holder as BaseRateHolder).bind(rate)
}
}
class RateHolder(
private val binding: RateItemBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: Rate) {
binding.apply {
rate = item
executePendingBindings()
}
}
}
class BaseRateHolder(
private val binding: BaseRateLayoutBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: Rate) {
binding.apply {
rate = item
executePendingBindings()
}
}
val value = binding.value
}
}
private class RateDiffCallback : DiffUtil.ItemCallback<Rate>() {
override fun areItemsTheSame(oldItem: Rate, newItem: Rate): Boolean {
return oldItem.currencyCode == newItem.currencyCode
}
override fun areContentsTheSame(oldItem: Rate, newItem: Rate): Boolean {
return oldItem.rate == newItem.rate
}
}
Here I used binding in the adapter, and if you are not familiar with it, I will be more than happy to explain as well
In the Activity:
Before the onCreate:
private val ratesAdapter = RatesAdapter()
In the onCreate:
ratesList.adapter = ratesAdapter
Whenever you need to update the adapter including the first time you need to call it:
ratesAdapter.submitList(rates)
Rate is the model class that I am using
rates is the mutable list of < Rate >
ratesList is the recyclerview
try this : notifyItemChanged
with this:
listView.setItemAnimator(null);

How to add ItemClick on PagedListAdapter?

Here is my first time to apply MVVM concept to my Android Application. I follow the steps at the referenced article
https://medium.com/swlh/realtime-firestore-pagination-on-android-with-mvvm-b5e30cea437
And I am managed to load data successfully. When it comes to implementing the onclick event at the row of my RecyclerView List, it comes out that there has no onlick response .
Would you please suggest the better method to implement the onCLick method given that I have applied PageListAdapter?
When I study the PageListAdapter documentation on Android, it seems no clue for me to implement the onclick method.
class MovieViewModel(movieRepository: MovieRepository) : ViewModel() {
private val viewModelJob = SupervisorJob()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
var selected: MutableLiveData<RealtimeMovie>? = null
private val config = PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setPrefetchDistance(10)
.setPageSize(20)
.build()
val records: LiveData<PagedList<RealtimeMovie>> =
LivePagedListBuilder<String, RealtimeMovie>(
MovieDataSource.Factory(movieRepository, uiScope),
config
).build()
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
}
Here is my adapter:
class MovieAdapter : PagedListAdapter<RealtimeMovie, MovieAdapter.MovieViewHolder>(
object : DiffUtil.ItemCallback<RealtimeMovie>() {
override fun areItemsTheSame(old: RealtimeMovie, new: RealtimeMovie): Boolean =
old.id == new.id
override fun areContentsTheSame(old: RealtimeMovie, new: RealtimeMovie): Boolean =
old == new
}
) {
private lateinit var onItemClick: (movie: RealtimeMovie) -> Unit
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieViewHolder {
val view = LayoutInflater.from(parent.context).inflate(
R.layout.list_movie,
parent,
false
)
return MovieViewHolder(view)
}
infix fun setOnItemClick(onClick: (movie: RealtimeMovie) -> Unit) {
this.onItemClick = onClick
}
override fun onBindViewHolder(holder: MovieViewHolder, position: Int) {
val record = getItem(position)
holder.bind(record)
holder.itemView.setOnClickListener { onItemClick(record!!) }
}
override fun onViewRecycled(holder: MovieViewHolder) {
super.onViewRecycled(holder)
holder.apply {
txtRecordName.text = ""
crdRecord.isEnabled = true
crdRecord.setCardBackgroundColor(
ContextCompat.getColor(
view.context,
android.R.color.white
)
)
viewHolderDisposables.clear()
}
}
inner class MovieViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
val viewHolderDisposables = CompositeDisposable()
val crdRecord by lazy { view.findViewById<MaterialCardView>(R.id.crd_record) }
val txtRecordName by lazy { view.findViewById<TextView>(R.id.txt_record_name) }
fun bind(RealtimeMovie: RealtimeMovie?) {
RealtimeMovie?.let {
it.record
.subscribeBy(
onNext = {
txtRecordName.text = it.title
},
onError = {
// Handle error here
// Record maybe deleted
}
)
.addTo(viewHolderDisposables)
}
}
}
}
Here is my fragment :
..
viewModel = ViewModelProviders.of(this, factory).get(MovieViewModel::class.java)
viewModel.records.observe(this, Observer {
swpRecords.isRefreshing = false
recordsAdapter.submitList(it)
recordsAdapter.setOnItemClick {
print("movie : ${it.id}" )
print("movie : ${it.record}" )
}
})

BoundaryCallback in PageList never gets called

I'm using Paging Library with PagedListAdapter, I'm using BoundaryCallback in PagedList to know when the user reaches the end of the list. The problem is that the method in my callback never gets called.
This is my data source code:
class PropertyDataSource : ItemKeyedDataSource<Int, Property>() {
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Property>) {
callback.onResult(getProperties(1, params.requestedLoadSize))
}
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Property>) {
Thread.sleep(1000)
callback.onResult(getProperties(params.key + 1, params.requestedLoadSize + params.key))
}
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Property>) {
}
override fun getKey(item: Property): Int {
return item.id
}
}
my code for boundary callback is
class MyBoundryCallBack: PagedList.BoundaryCallback<Property>() {
override fun onItemAtEndLoaded(itemAtEnd: Property) {
Log.e("alz", "at end $itemAtEnd")
}
override fun onItemAtFrontLoaded(itemAtFront: Property) {
Log.e("alz", "at front $itemAtFront")
}
override fun onZeroItemsLoaded() {
Log.e("alz", "zero item loaded")
}
}
here is my activity code
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val adapter = MyListAdapter(PropertyDiffCallback())
mainRecyclerView.adapter = adapter
mainRecyclerView.layoutManager = LinearLayoutManager(this)
val propertyDataSourceFactory = PropertyDataSourceFactory()
val config = PagedList.Config.Builder()
.setPageSize(2)
.setInitialLoadSizeHint(3)
.setEnablePlaceholders(false)
.build()
val pagedList = LivePagedListBuilder(propertyDataSourceFactory, config)
.setBoundaryCallback(MyBoundryCallBack())
.build()
pagedList.observe(this, Observer {
adapter.submitList(it)
}
}
And my code for Adapter is look like this
class MyListAdapter(diffCallback: DiffUtil.ItemCallback<Property>) :
PagedListAdapter<Property, RecyclerView.ViewHolder>(diffCallback) {
class ItemHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
val titleTV: TextView = itemView.findViewById(R.id.title)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return ItemHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_property, parent, false))
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if(getItem(position) == null) return
val itemHolder = holder as ItemHolder
itemHolder.titleTV.text = getItem(position)!!.title
}
}
And here is my implementation of getProperties() that will return some mock data. Later on, I'm going to change it to load data from the server.
fun getProperties(from: Int, to: Int): List<Property> {
val list = ArrayList<Property>()
for (i in from..to){
list.add(Property(i, "item $i"))
}
return list
}
Note that, my code works fine and my adapter requesting more data as it goes to the end, I just don't get the callback when PagedList gets to the end.

Categories

Resources