An alternative approach to displaying load during pagination

image



Working with page-by-page loading in mobile applications is a fairly simple topic and does not have any pitfalls.



The developer encounters a similar task quite often and, accordingly, over and over again doing the same thing is boring and lazy.



But even the most ordinary task can be solved in another, more interesting way, to gain new knowledge and just stretch your brain.



Pagination



In short, all the work to implement pagination consists of the items listed below:



  1. Add listener to recycler view;
  2. Download primary data;
  3. Catch a callback when the user scrolls through the list;
  4. Show download in the list after all items;
  5. Send a request for new items;
  6. Display data again.


In our case, the page paging mechanism was implemented, but there was no display of the load while the user was scrolling through the list.



Ok, doing this once in one application is not a problem. If there are several screens where this functionality is needed, then you can write a basic adapter that can work with an additional type of view.



But what if the task is somewhat more complicated?



Let's say there are several not new projects that have one core module with basic functionality.



Since the projects are far from the first year, as a result, a lot of legacy code has been accumulated related to this task, namely, many adapters with different base classes.



How can I solve the problem?



Create a new base adapter that will be able to show the progress of the download, rewrite all the old adapters + screens that use the adapters, because you have to control the display state of the load on each screen, in each presenter or view.



This can be quite time consuming. And also large refactoring can lead to a number of new errors, especially if the test coverage in projects tends to zero. Plus, a high load on the QA department with regression testing of all screens with pagination for several applications.



It would be great to write such code that requires a minimum amount of time from the development client to integrate the basic solution in its feature. So that you do not have to implement the control logic of displaying the progress of loading - showing and hiding.



At this point, I thought about using the ItemDecoration class.



Why not use this class to encapsulate all work related to





Unfortunately, not a single ready-made solution was found on the Internet - either no one tried to implement it, or simply did not share experience after implementation.



What is needed to implement loading display during pagination using ItemDecoration?





Indentation



Any developer who has created SpacingItemDecoration at least once knows how to indent. The ItemDecoration getItemOffsets method will help us with this:



override fun getItemOffsets(outRect: Rect, view: View, recyclerView: RecyclerView, state: RecyclerView.State) { super.getItemOffsets(outRect, view, parent, state) }
      
      





For any element of the list, we can add indents on either side. Specifically, in our case, we need to understand that the user has rolled the list to the end and at the last element indent from the bottom equal to the value of the future displayed download progress + indents from the progress itself.



How to understand that the list is scrolled to the bottom?



The code below will help us with this:



  private fun isLastItem(recyclerView: RecyclerView, view: View): Boolean { val lastItemPos = recyclerView.getChildAdapterPosition(view) return lastItemPos == recyclerView.adapter!!.itemCount - 1 }
      
      





In total, we have code for defining and implementing indentation:



  override fun getItemOffsets(outRect: Rect, view: View, recyclerView: RecyclerView, state: RecyclerView.State) { super.getItemOffsets(outRect, view, recyclerView, state) when (isLastItem(recyclerView, view)) { true -> outRect.set(Rect(0, 0, 0, 120)) else -> outRect.set(Rect(0, 0, 0, 0)) } } private fun isLastItem(recyclerView: RecyclerView, view: View): Boolean { val lastItemPos = recyclerView.getChildAdapterPosition(view) return lastItemPos == recyclerView.adapter!!.itemCount - 1 }
      
      





A third of the work is done!



We determine the time of displaying the progress of the download and call the progress drawing



The ItemDecoration onDrawOver method will help us with this:



  override fun onDrawOver(canvas: Canvas, recyclerView: RecyclerView, state: RecyclerView.State) { super.onDrawOver(canvas, recyclerView, state) }
      
      





The onDrawOver method differs from the onDraw method only in the rendering order. OnDrawOver decorations will only be rendered after the list item itself has been drawn.



In the onDrawOver method, we need to understand at what point we need to draw the progress, at what point to remove it and cause updates to the list items in order to hide the already unnecessary indent.



Code for implementing the actions described above:



  override fun onDrawOver(canvas: Canvas, recyclerView: RecyclerView, state: RecyclerView.State) { super.onDrawOver(canvas, recyclerView, state) when (showLoading(recyclerView)) { true -> { PaginationProgressDrawer.drawSpinner(recyclerView, canvas) isProgressVisible = true } else -> { if (isProgressVisible) { isProgressVisible = false recyclerView.invalidateItemDecorations() } } } } private fun showLoading(recyclerView: RecyclerView): Boolean { val manager = recyclerView.layoutManager as LinearLayoutManager val lastVisibleItemPos = manager.findLastCompletelyVisibleItemPosition() return lastVisibleItemPos != -1 && lastVisibleItemPos >= recyclerView.adapter!!.itemCount - 1 }
      
      





Drawing progress



The rendering code is quite voluminous and will be presented in a separate file, a link to which I will provide below.



I would like to dwell only on the nuances that are necessary for implementation.



All drawing work is done on canvas. Accordingly, it will be necessary to configure an instance of the Paint object and draw arcs, indicating the starting and ending angles.



An important nuance is that in order to render as similar as possible to a normal ProgressBar, you need to create your own analog of an interpolator so that the rendering animation happens non-linearly.



To understand the work of interpolators and to understand what you want to get from your animation, I recommend that you familiarize yourself with the graphs that depict the work of various types of interpolators, for example, in this article



Code References:



PaginationLoadingDecoration

PaginationProgressDrawer



If necessary, you can create a ProgressDrawer interface and replace implementations in PaginationLoadingDecoration.



Download Demo Video:





Thank you for reading, enjoy coding :)




All Articles