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 one Adapter 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:

  • LoadState, exposed from the ViewModel
  • A load state header and footer layout
  • ViewHolder object for the header and footer
  • ListAdapter that displays 0 or 1 items based on the LoadState. Every time the LoadState 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 ViewHolders, 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_IDSISOLATED_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.notifyDataSetChangedMergeAdapter 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

Popular posts from this blog

Android App Project Package Structure (Android Studio)

ImageView and ImageButton in Android

Developing Android unit and instrumentation tests - Tutorial