Reputation: 303
Hello there recently I switch my project from java to kotlin and I'm a very beginner in kotlin, and I want to implement pagination in my recycler view
the recycler view contain images that are stored in firebase, what I want
Option 1 is that first when the fragment is created it loads 20 images and when the user reaches the last image (20) it loads the next 20 images in the recycler view
Option 2
(optional but wanted this kind of pagination in my app )
have you seen Pinterest and Instagram, if you just scroll normally you probably don't see the images are loading until if you scroll very fast, and then it shows the progress bar?
for eg: Pinterest
1: scroll normally or slow Link
2: scroll very fast Link
I want this kind of functionality if possible
what I think is going on in Instagram or Pinterest is for eg
They load probably 20 images at the start and when the user reaches the 10th image (still 10 images are remaining to be seen by the user) they just load the next 10 images so it just maintains the adapter with 20 images instead of reaching the end of the list to load the next images ( hence this would be an exception if user scrolls very fast)
Note: I just don't know what it is said pagination or something else and also not sure that what I said up are the technique used I'm just speculating
Code
Home_Fragment.kt
I only included the required code but if you want more references please tell me I will update the question btw the implemented pagination (don't know is it pagination or something else but it's not working)
val staggeredGridLayoutManager =
StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.VERTICAL)
postRecyclerView.layoutManager = staggeredGridLayoutManager
postRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
visibleItemCount = staggeredGridLayoutManager.childCount
totalItemCount = staggeredGridLayoutManager.itemCount
val firstVisibleItems: IntArray? = null
staggeredGridLayoutManager.findFirstVisibleItemPositions(firstVisibleItems)
if (loading) {
if (totalItemCount > previousTotal) {
loading = false
previousTotal = totalItemCount
}
}
if (!loading && totalItemCount - visibleItemCount
<= firstVisibleItem + visibleThreshold
) {
data
loading = true
}
}
})
data
// setupFirebaseAuth();
shimmerFrameLayout!!.startShimmer()
mUploads = ArrayList()
postsAdapter = PostAdapter_Home(requireContext(), mUploads)
postRecyclerView.adapter = postsAdapter
postRecyclerView.scrollToPosition(saved_position)
// postsAdapter.stateRestorationPolicy = PREVENT_WHEN_EMPTY;
return view
}
private val data: Unit
get() {
databaseReference.addValueEventListener(object : ValueEventListener {
@SuppressLint("NotifyDataSetChanged")
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.exists()) {
shimmerFrameLayout!!.stopShimmer()
shimmerFrameLayout!!.visibility = View.GONE
postRecyclerView.visibility = View.VISIBLE
mUploads!!.clear()
for (dataSnapshot in snapshot.children) {
val upload = dataSnapshot.getValue(Upload::class.java)!!
upload.setmKey(dataSnapshot.key)
mUploads!!.add(upload)
}
}
postsAdapter!!.setUploads(mUploads)
//notify the adapter
postsAdapter!!.notifyItemRangeInserted(0, mUploads!!.size)
loading = true
}
override fun onCancelled(error: DatabaseError) {
loading = true
}
})
}
PostAdapter_Home.kt
class PostAdapter_Home(var mcontext: Context, var mUploads: MutableList<Upload?>?) :
RecyclerView.Adapter<PostAdapter_Home.PostViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostViewHolder {
val view: View
view = LayoutInflater.from(mcontext)
.inflate(R.layout.post_item_container_home_ex, parent, false)
return PostViewHolder(view)
}
override fun onBindViewHolder(holder: PostViewHolder, position: Int) {
val shimmer = ColorHighlightBuilder()
.setBaseColor(Color.parseColor("#F3F3F3"))
.setBaseAlpha(1f)
.setHighlightColor(Color.parseColor("#E7E7E7"))
.setHighlightAlpha(1f)
.setDropoff(50f)
.build()
val shimmerDrawable = ShimmerDrawable()
shimmerDrawable.setShimmer(shimmer)
val uploadCurrent = mUploads?.get(position)
Glide.with(mcontext)
.load(uploadCurrent?.getmImageUrl())
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
.placeholder(shimmerDrawable)
.fitCenter()
.into(holder.imageView)
}
override fun getItemCount(): Int {
return mUploads?.size!!
}
// public long getId() {
// return this.id;
// }
//
// @Override
// public long getItemId(int position) {
// return mUploads.get(position).Id;
// }
fun setUploads(uploads: MutableList<Upload?>?) {
mUploads = uploads
}
class PostViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val imageView: ShapeableImageView
init {
imageView = itemView.findViewById(R.id.imagePostHome)
}
}
}
Upload.kt
package com.example.myappnotfinal.AdaptersAndMore
import com.google.firebase.database.Exclude
class Upload {
private var mImageUrl: String? = null
private var mKey: String? = null
constructor() {}
constructor(imageUrl: String?) {
mImageUrl = imageUrl
}
fun getmImageUrl(): String? {
return mImageUrl
}
fun setmImageUrl(mImageUrl: String?) {
this.mImageUrl = mImageUrl
}
@Exclude
fun getmKey(): String? {
return mKey
}
@Exclude
fun setmKey(Key: String?) {
mKey = Key
}
}
Upvotes: 3
Views: 12344
Reputation: 1669
Best will be to use Android's Paging Library with PagingDataAdapter
for RV,
you can read Pro's and Con's online,
It solves your problem for
loading Paged Data
Since it loads next page before, you wont have to worry about slow and fast scroll
You need to create a PagingSource for loading data
class PagingSource<T: BaseModel> (
private val endPoint : suspend (Int) -> ApiResult<PageResult<T?>>,
private val filters : suspend (T) -> Boolean = {true}
) : PagingSource<Int ,T>() {
companion object{
const val DEFAULT_STARTING_PAGE_INDEX = 0
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, T> {
Timber.d("load Called for : ${endPoint::class}")
val pageIndex = params.key ?: DEFAULT_STARTING_PAGE_INDEX
val responsePageable = endPoint(pageIndex)
val responseData : MutableList<T> = mutableListOf()
val nextKey: Int?
when( responsePageable ) {
is ApiResult.Success -> {
if(responsePageable.data.content.isNotEmpty()) {
nextKey = pageIndex + 1
responseData.addAll(responsePageable.data.content
.filterNotNull()
.filter { filters(it) }
)
}
else {
nextKey = null
}
}
is ApiResult.NetworkError -> {
return LoadResult.Error(
Throwable("No network connection!")
)
}
else -> {
nextKey = null
}
}
return LoadResult.Page(
data = responseData ,
prevKey = if(pageIndex == DEFAULT_STARTING_PAGE_INDEX) null else pageIndex ,
nextKey = nextKey
)
}
/**
* The refresh key is used for subsequent calls to PagingSource.Load after the initial load.
*/
override fun getRefreshKey(state: PagingState<Int, T>): 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)
}
}
}
above is my custom impl of paging source ref
It uses ApiResult
as a response wrapper , you can replace it with Response
(Retrofit Default) or relevent API_RESULT files
Here's the example of how to use above PagingSource
fun getCoreCharacters(
sortParam: Character.Companion.SortCharacter ,
filters: suspend (Character) -> Boolean
) : Flow<PagingData<Character>>{
return Pager(
config = PagingConfig(
pageSize = DEFAULT_PAGE_SIZE,
enablePlaceholders = false
),
pagingSourceFactory = {
PagingSource(endPoint = loadMore@{ x: Int ->
characterApi.getCoreCharacters(x ,sortParam.value)
},
filters = filters
)
}
).flow
}
Upvotes: 2