Adapter’s composition

Yet another way to eliminate the Adapter Hell

Recently, when I chose the theme for my next article, I read Hannes Dorfmann’s great article about a problem in many-lists-application called “Adapter Hell”. In short, when you create more or less complex application with a bunch of lists, you will face the problem of adapter’s code duplication. And you have one of the opportunities:

  • you can remain duplication and write each time new adapter according to copy-paste principle;
  • you can eliminate boilerplate code and duplication.

There are several ways to do that, and one of the obvious for my opinion is inheritance. But, as was said in the aforementioned article, unfortunately, you will end up with adapter hell recently. Moreover, you will have to produce new duplications, repeat your adapter code again and again because of java’s inheritance limitations. In this article, I will try to uncover “Adapter organization” topic a little bit more.

Part I. Adapter decoration

Assume for example that you have an adapter of uniform items (we can a have list of text posts, or news, audio, cars for sale and e.t.c). And you want to add for some of them rating bar, that will appear between first and second element. Something like “Please rate our application”.

In the screenshot, we have such case. So for some lists, we want to put rating bar and show it (or hide, when user rates application or presses close button). In this case, we, of course, could use inheritance. We could have an abstract RateAdapter class and then inherit it by only needing adapters. And it will work well. But then suddenly we need to add advertisement banner for some of the lists (suppose between fifth and sixth items). So we will have just lists with items, lists with items and rating bar, lists with items and advertisement; and list with items and both banner and rating. How can we organize our Adapter Hierarchy? We have no multiple inheritance, so we will use RatingAdapter, it inheritor will be AdvertisementAdapter and all adapters, that will need to show rating or advertisement, inherit AdvertisementAdapter. But this is not good, since it is not obvious, that inheritor of AdvertisementAdapter will also have ability to show rating. Moreover, maybe it doesn’t need to show rating, but it has this possibility, and this is a potential bug in future.

So the first way to solve this issue is to introduce adapter decoration. Yeah, I know, it’s pretty exotic, since RecyclerView.Adapter does not interface and thus contains some logic behind, so introducing decorator can be a potential risk for the stability of the application. Moreover, its complexity forces us to write a subscription mechanism, to provide a more convenient mechanism for updating elements. But, my aim is to provide additional options of working with adapters:

First class is our RateAdapter, our wrapper, our decorator, that applies any recycler adapter through the constructor and then inserts rating item between origin’s elements. Also, we need to provide additional logic and functionality for it, but that’s not the point. The point is, that though decoration we get rid of inheritance.

It’s not so hard to evolate this class to abstract one that can inset not one but set of items. And after introducing, for example, RateAdapter and AdvertisementAdapter classes we can apply Kotlin’s extension sugar to that:

Drawbacks

The drawbacks of this approach in a case of RecyclerView’s adapter from my point of view is more strong than advantages:

  • First of all, we need to create and manage observation mechanism between adapters. Thus when wrapped adapter will update its items, wrapper adapter will get notified about that and will be able to pass notification up to RecyclerView.
  • Second, it’s pretty hard to manage adapter’s behavior from outside. To do that you will need to keep a reference to all wrapper layers. And it can be pretty strange to see in a fragment two or three…or four adapter fields. We can slightly improve it by aggregating all adapters in one manager that will delegate responsibilities to appropriate adapters.
class AdapterAggregator {
var rateAdapter: RateAdapter? = null
var advertisementAdapter
: AdvertisementAdapter? = null

fun
buildAdapter() = NewsAdapter()
.withRating().let { rateAdapter = it }
.withAdvertisement().let { advertisementAdapter = it }

fun
showRating() = rateAdapter?.showRating()

fun hideRating() = rateAdapter?.showRating()

fun showAdvertisement() = advertisementAdapter?.showAdvertisement()

fun hideAdvertisement() = advertisementAdapter?.hideAdvertisement()
}

With introducing appropriate interfaces and Kotlin’s delegation we will have:

interface RatingInteractor {
fun showRating()
fun hideRating()
}

interface AdvertisementInteractor {
fun showAdvertisement()
fun hideAdvertisement()
}

class AdapterAggregator(rateAdapter: RateAdapter) {
var rateAdapter: RateAdapter? = null
var advertisementAdapter
: AdvertisementAdapter? = null

fun
buildAdapter() = NewsAdapter()
.withRating().let { rateAdapter = it }
.withAdvertisement().let { advertisementAdapter = it }
}

class AdapterDelegator(adapterAggregator: AdapterAggregator):
RatingInteractor by adapterAggregator.rateAdapter,
AdvertisementInteractor by adapterAggregator.advertisementAdapter

But it will not cancel drawbacks of that approach.

PART II. Each item is binder

Another approach to preventing using inheritance looks similar to Hannes Dorfmann solution. We will make one universal adapter. But the difference is that each item is responsible for binding itself instead of moving this responsibility to special delegators. To do that, we need to declare an interface AdapterItem:

interface AdapterItem<T: RecyclerView.ViewHolder> {
val viewHolderProducer: HolderProducer<T>
fun bindView(viewHolder: T, position: Int)
}

Its implementation will be responsible for binding data to view and provision special factory HolderProducer for creating new view holders.

interface HolderProducer<T: RecyclerView.ViewHolder> {
fun produce(parentView: ViewGroup): T
}

Also, we need to think how we can organize generating different id for different element types. To do that we introduce a MutableList decorator:

So the process will be constructed in this way:

  1. We have a list of data. It can be anything.
  2. We wrap each element with its own specific AdapterItem implementation.
  3. We add all result items to RecyclerItems list
  4. We pass this list to a special adapter. Here it is:

Drawbacks

Despite an opportunity to forget about item types problem when using aforementioned approach, you will face with next flaws:

  • You will have to create a new object for each element in the list to wrap it in AdapterItem for using with such adapter. As alternative instead of composing model in AdapterItem you can inherit model classes with implementations of AdapterItem. For example:
  • If we will have the case of the changeable item, we will need to think about binding AdapterItem and AbstractMultiTypeRecyclerAdapter together.

PART III. Databinding

The last solution is to using Databinding for erasing code boilerplate. It is not fully solution of ‘Adapter inheritance’ problem but rather a way to simplify your code. Thus, it can be easily used in conjunction with first two approaches and with Hannes Dorfmann delegates. Here is how the adapter will look like:

That’s all! You don’t need to think about item type and e.t.c. Moreover, we can move getItemViewType function logic out and pass to BindingAdapter layout generator:

Drawbacks

As always, this method of solution have own defects and limitations:

  • For layout you have to use xml-s only. This is pretty obvious, but still. Since you are using databinding library, you need to define you item layout only in xml files
  • Each layout that wants to bind item, must have variable item in it definition like that:
<data>
<variable name="item" type="ItemType"/>
</data>

PART IV. Few words about comparison

To summarize you have at least five or six ways to manage adapters. You can:

  • Create a new adapter class each time you need new adapting mechanism
  • Use inheritance to bring some adapter management and reduce duplications
  • Implement adapter delegators to separate binding of different types from each other
  • Use decoration to allow compose adapters instead of building rigid inheritance hierarchy
  • Allow recognizing each element as binding mechanism
  • Apply data-binding for fast implementation of universal adapter

And, as for me, there is no silver bullet. You can not just choose one of the ways and say: “Well, yeah, buddy. I will use that one. Other ways just for dummies”. Well, no. I think that we need to always find the best option for each problem individually.

If you have a simple application or an application that have a small amount of lists. Or maybe it has many lists, but all of them are static (they not dynamically changes, adding new types of items in runtime) and have one type of items. In such cases, you don’t need to create a bunch of delegators or wrap every element in AdapterItem implementor. You just need to create a simple adapter for each list. And then refactor it: create an abstract base adapter and put all common adapter stuff in it, then in children you just need to add binding and inflating mechanisms. No delegators, no wrappers, no decorators. Simple and effective.

If you have multitype list but no rocket science. You know, just lists that have different elements. For example, posts on a profile page. You can have, pictures, music, videos, texts. All of them are different, so you need an adapter that handles different types of items. In that case, delegators of Hannes Dorfmann can help you. Because you don’t need to paste new types in runtime, you don’t need to paste some special item in the middle of the list. You just don’t have that. You have only list of different posts and you want to show it, not littering the adapter class.

Now suppose you have an application. And then product owner comes to say: “Hey, we need to place ratings on those lists and advertisement in that lists”. In that case, you can implement adapter decorators because you just know what to place and where in lists you want to place that, but you don’t know what kind of lists you need to extend with that functionality. So decorators of best for that tasks. Consider, you have a user posts lists and also, in each post you have an list of references. In sum, you have two types of lists, but you need to show rating not in all lists, but in a random couple of them. You don’t care whether it posts of references, you just keep a random number of them and insert rating bar. With this condition, neither PostsAdapter nor ReferencesAdapter should not know about rating. You need to define that in runtime. And decorator is the best for that.

In the case when you need to work with complex lists, that should be filled with new info, new items, new types dynamically, you can use AdapterItem wrapper for each list element. If you afraid of a lot of memory allocation, you can extend the model and implement AdapterItem interface by its inheritor. But the main point of that approach is that you don’t need to think about what types need to handle that adapter. You move resolution of this problem in runtime, so you take the opportunity to tune a list, an adapter whenever you want. You had a list of text posts and that audio posts came out? You don’t need to add a new type for your adapter just wrap your model and add items to list.

And if you can deal with limitations described in PART III then go and inject data binding in your adapters and you will clean adapter/delegators/decorators/items code because all binding stuff will happen in an XML file.

Summary

List are a common part of all application. And dealing with adapters is one of the basic parts of constructing an application. I’m sure that you have some solutions of “Adapter Hell” problem in mind. Now, in addition, you have those three ways. By the way, I will glad, if you will share your approaches to handle adapters in comments below.

Thank you for reading this post.

Love being creative to solve some problems with an simple and elegant ways