Merge adapters sequentially with MergeAdapter
MergeAdapter
is a new class available in recyclerview:1.2.0-alpha02
which enables you to sequentially combine multiple adapters
to be displayed in a single RecyclerView
. This enables you to better encapsulate your adapters rather than having to combine many data sources into a single adapter, keeping them focused and re-usable.
To learn android course visit:android app development course
One use case for this is displaying a list loading state in a header or footer: when the list is retrieving data from the network, we want to show a progress spinner; in case of error, we want to show the error and a retry button.
Introducing MergeAdapter
MergeAdapter
allows us to display the contents of multiple adapters, in a sequence. For example, let’s say that we have the following 3 adapters:
val firstAdapter: FirstAdapter = … val secondAdapter: SecondAdapter = … val thirdAdapter: ThirdAdapter = … val mergeAdapter = MergeAdapter(firstAdapter, secondAdapter, thirdAdapter) recyclerView.adapter = mergeAdapter
The recyclerView
will display the items from each adapter sequentially.
Having different adapters allows you to better separate the concerns of each sequential part of a list. For example, if you want to display a header, you don’t need to put the logic related to the header display in the same adapter that handles the list display, rather you can encapsulate it in its own adapter.
Displaying load state in a header and footer
Our header/footer displays either a progress indicator or reports an error. When the list has successfully finished loading, the header/footer shouldn’t display anything. Therefore they can be represented as a list with 0 or 1 items, with their own adapter:
val mergeAdapter = MergeAdapter(headerAdapter, listAdapter, footerAdapter) recyclerView.adapter = mergeAdapter If both the header and the footer use the same layout,ViewHolder
and UI logic (e.g when progress is displayed and how), you can implement just oneAdapter
class and create 2 instances of it: one for the header and one for the footer.
For a complete implementation, check out this pull request, which adds:
- A
LoadState
, exposed from theViewModel
- A load state header and footer layout
- A
ViewHolder
object for the header and footer - A
ListAdapter
that displays 0 or 1 items based on theLoadState
. Every time theLoadState
changes, we notify that the item needs to be changed, inserted or removed (see code).
ViewHolders
By default, each adapter maintains their own pool of ViewHolder
s, with no re-use in between adapters. If multiple adapters display the same ViewHolder
, we may want to reuse instances between them. We can achieve this by constructing our MergeAdapter
with a MergeAdapter.Config
object, where isolateViewTypes = false
. Like this, all the adapters merged will use the same view pool. In the loading status header and footer example, both ViewHolders
will actually display the same content so we could reuse them.
To support different ViewHolder
types, you should implement Adapter.getItemViewType
. When you’re reusing ViewHolders
, make sure that the same view type doesn’t point to different ViewHolders
! One best practice for this is to return the layout ID as the view type.
<!-- Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.0 --> class HeaderAdapter() : RecyclerView.Adapter<LoadingStateViewHolderHeaderViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { return LoadingStateViewHolder(parent)}
override fun getItemViewType(position: Int): Int {- return 0+ return R.layout.list_loading }}
class FooterAdapter() : RecyclerView.Adapter<LoadingStateViewHolderFooterViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { return LoadingStateViewHolder(parent)}
override fun getItemViewType(position: Int): Int {- return 0+ return R.layout.list_loading }}
Using stable ids
Instead of using stable ids along with notifyDataSetChanged
, it is recommended to use the specific notify events of the adapter that give the RecyclerView
more information about the changes in the data set. This allows the RecyclerView
to update the UI more efficiently and with better animations. If you’re using ListAdapter
then the notify events are handled for you, under the hood, with the help of the DiffUtil
callback. But if you do need to use stable ids, the MergeAdapter.Config
provides 3 different configurations for stable ids: NO_STABLE_IDS
, ISOLATED_STABLE_IDS
and SHARED_STABLE_IDS
. The last two require you to handle stable ids in your adapter. Check out the StableIdMode
documentation for more information on how they work.
Data changes notifications
When an adapter part of a MergeAdapter
calls one of the notify functions, the MergeAdapter
computes the new item positions before updating the RecyclerView
.
From the RecyclerView
’s perspective, notifyItemRangeChanged
means items are the same, just their contents changed. notifyDataSetChanged
means there is no relation between before and after. Hence, we cannot map notifyDataSetChanged
into notifyItemRangeChanged
.
If an adapter calls Adapter.notifyDataSetChanged
, MergeAdapter
will also call Adapter.notifyDataSetChanged
, rather than Adapter.notifyItemRangeChanged
. As usual with RecyclerViews
avoid calling Adapter.notifyDataSetChanged()
, prefer more granular updates or use an Adapter
implementation that does this automatically, like ListAdapter
or SortedList
.
To know more information visit:best android development course
Finding ViewHolder position
You might have used ViewHolder.getAdapterPosition
in the past to get the position of a ViewHolder
in the adapter. Now, because we’re merging multiple adapters, use ViewHolder.getBindingAdapterPosition()
. If you want to get the adapter that last bound a ViewHolder
, in the case where you’re sharing ViewHolders
, use ViewHolder.getBindingAdapter()
.
That’s all! If you want to sequentially show different types of data that would benefit from being encapsulated in their own adapters, start using MergeAdapter
. For advanced control of ViewHolder
pool and stable ids, use MergeAdapter.Config
.
Comments
Post a Comment