RecyclerView — Delegate Adapters & Data Classes with Kotlin

2
279
Kotlin Tutorial in Android Studio
Kotlin Tutorial in Android Studio

If this is the first time that you read this tutorial, please I encourage you to read the Introduction part just to get a general idea about what we are going to be developing in this tutorial.

Introduction

In this 4th Part we are going to cover these topics about Kotlin:

  • Init Constructor
  • Object Expressions
  • Single Expressions
  • Data Classes
  • Ranges
  • List & Lambdas (introduction)

Each topic will be inside the context of creating our NewsAdapter adapter and using the Delegate Adapter pattern.

So let’s start creating our adapter and show something in the screen!

Creating the NewsAdapter

We are going to create a new adapter for our RecyclerView. In this case, we are going to do it with a pattern called “Delegate Adapter”, we use it in our company and it was inspired by this article by Hannes Dorfmann.

Our Adapter will have a list of delegated adapters, those are going to be in charge of knowing how to inflate and return a specific view in the RecyclerView. The general idea behind of this is to receive a list of ViewTypeitems and according to this ViewType get the corresponding Delegate Adapter to inflate and populate the view for this item. The item will be used to be passed as argument to the delegated adapter so the item can be used to extract specific data for this View.

The main idea behind of this is to match items (ViewType objects) with specific adapters (Delegated Adapters) like you can see in this image:

Thanks to the ViewType, we know what delegated adapter we need to use to create the View for this item. In our case, the items will be a list of news and at the end of the list we will add a loading item to give the idea that more news are coming. So we will need two delegated adapters, one for the News and another for the Loading.

This approach gives you a lot of flexibility to add new Views to your RecyclerView, just add a new delegated adapter that correspond to a new ViewType and that’s all! for example, we could add a new ViewType for Promotions and the corresponding Delegate Adapter to inflate a view with promotions.

Let’s continue learning more about the classes and interfaces that we need to accomplish this.

ViewType

Is an interface that we will use for the items that we are going to show in the RecyclerView. Each item must implement this interface so we can ask to each item the ViewType type (int value) and search for the corresponding delegated adapter to this type:

interface ViewType {
    fun getViewType(): Int
}

In our Adapter we will have a ViewType’s list called items:

private var items: ArrayList<ViewType>

Here we are going to store the news and the loading item. Our news model will extend from ViewType and we are going to create a new item locally for the Loading item.

Let’s add a Loader to our recycler view

We need 3 things:

  • The ViewType Type: This will be an integer value (like an ID) to match our ViewType with the delegated adapter (AdapterConstants.LOADING).
  • The ViewType Item: This will be an object that implements the ViewType interface and return the ViewType Type (ID). This will allow us to insert this item to the items list and tell the adapter to render this view.
  • The Loading Delegate Adapter: which will be in charge of inflating the view and return it to our NewsAdapter.

Loading Delegate Adapter Impl

class LoadingDelegateAdapter : ViewTypeDelegateAdapter {

    override fun onCreateViewHolder(parent: ViewGroup) = TurnsViewHolder(parent)

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, item: ViewType) {
    }

    class TurnsViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder(
            parent.inflate(R.layout.news_item_loading)) {
    }
}

As you can see we are creating our Delegate Adapter by extending another interface called “ViewTypeDelegateAdapter”. This interface allow us to have a generic list of delegate adapters and invoke those methods without requiring us to have in our NewsAdapter to know nothing specific of the delegated adapters implementation. One method to create the ViewHolder and another to bind it.

Bind ViewType Type with a Delegate Adapter

We are going to use a map in order to bind a ViewType type with a delegated adapter:

private var delegateAdapters = SparseArrayCompat<ViewTypeDelegateAdapter>()
init {
    delegateAdapters.put(AdapterConstants.LOADING, LoadingDelegateAdapter())
    ...
}

Init Constructor

init is the reserved word in Kotlin for a constructor of a class (in this case NewsAdapter), here we are going to initialize this map adding every ViewType type with the corresponding delegated adapter. In this case:

AdapterConstants.LOADING > LoadingDelegateAdapter()

In order to create a new object in Kotlin you don’t need to use the “new” keyword.

Loading ViewType Item

Let’s create our Loading item, this item will help us to render the Loading view in the position that we insert it in the items list:

private val loadingItem = object : ViewType {
    override fun getViewType() : Int {
        return AdapterConstants.LOADING
    }
}

Also add this item by default as the first item to render in the list:

init {
    delegateAdapters.put(...)
    items = ArrayList()
    items.add(loadingItem)
}

Object Expressions

In Kotlin you have something called “Object expressions”, which works in a similar way as anonymous inner classes in Java, and allows you to create an object without explicitly declaring a new subclass for it. In this case we are using it to define our loadingItem without creating a new class. The syntax is really intuitive and as you can see we are extending from ViewType and implementing the required interface.

Single Expressions

The getViewType() method has only a single expression function inside the body. In Kotlin we can take advantage of this and convert this method:

override fun getViewType() : Int {
    return AdapterConstants.LOADING
}

To this:

override fun getViewType() = AdapterConstants.LOADING

It’s like we are assigning the value AdapterConstants.LOADING to a function. This is a short way to do the same and it’s really more concise. Also you don’t have to specify the return value type as it can be inferred by the context. So this is how it looks now:

private val loadingItem = object : ViewType {
    override fun getViewType() = AdapterConstants.LOADING
}

As the concept of Delegate Adapters was explained, I’m not going to continue explaining every detail but to focus on Kotlin concepts in the rest of the tutorial.

News Item with Data Classes

Before creating our News delegate adapter and configure the NewsAdapter to receive a list of news, we need our UI object to represent each news. In Java we would normally create a class like this:

public class RedditNewsItem {

    private String author;
    private String title;

    public MyNews(String author, String title) {
        this.author = author;
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}

Again, Kotlin has come to help us with a new class type called “data class” which brings you a lot of benefits and for the same example it looks like this:

data class RedditNewsItem(var author: String, var title: String)

With this simple line you are doing the same as the previous Java code, it means that author and title has their own getters and setters and it requires a constructor with this two elements. This is just AMAZING! but wait! you still receive more benefits if you take this data class, you receive for free for this class:

  • equals/hashCode pair.
  • toString() with all the properties included.
  • copy() method to make easily a copy of your object.
  • and other useful methods that we are not going to see here but you can see more in the official page.

Also we need to make our class to extend ViewType so it can be included in the NewsAdapter list as another item, in this case as a News item.

Creating NewsDelegateAdapter

Now that we have our News item created, we need our delegate adapter which will be in charge of creating the view. Here you have a preview of what we need:

Preview of just one news

So we need to:

  • Show some texts like title, author, comments, etc
  • An image, we are going to use Picasso (see the build.gradle file in the commit below)
  • An Extension Function to show the time in this way.

Here you have all the code added to create the NewsDelegateAdapter as we are not going to go throw each detail.

Let’s review some Kotlin stuff:

Extension Function for Picasso

Allows me to make an ImageView to load it’s image directly:

fun ImageView.loadImg(imageUrl: String) {
    if (TextUtils.isEmpty(imageUrl)) {
        Picasso.with(context).load(R.mipmap.ic_launcher).into(this)
    } else {
        Picasso.with(context).load(imageUrl).into(this)
    }
}

Extension Function for Time

We will be receiving from Reddit, the time of a comment in a long format, so what we are doing here is to convert a Long type to a String with the format like “3 days and 1 minute ago”:

fun Long.getFriendlyTime(): String {
    // logic here...
}

Checkout the file called “TimeExt.kt” to see the code. For sure, it can be improved but for the purpose of this example it’s perfect 🙂

Android Extensions for News View

Checkout the file “NewsDelegateAdapter.kt” and you will see that we are also using Android Extensions for this view (like we did with the NewsFragment) but in this case we are adding to the synthetic package an extra value called “view” at the end of the layout name:

import kotlinx.android.synthetic.main.news_item.view.*

This is the way that Android Extensions allows you to still bind a view with code without being in the context of an Activity or Fragment (like in this case).

Update NewsAdapter to receive News

Let’s modify our NewsAdapter to receive a list of mocked news to be shown with our news and loading delegated adapters:

Range

Kotlin allows you to create in an easy way a range of numbers (Int, Long and Char types) by using an expression like this: “1..10”, this will return an IntRange (as we are using Ints in this example), IntRange extends from IntProgression and this implements Iterable. Thanks to this is that we can iterate over the range that we have, in this case from 1 to 10 to create our mock data:

for (i in 1..10) {
    ...
}

You can control the steps in the range and make it decremental like “10 downTo 1”. More about this you can find it here.

mutableListOf

This is a Kotlin function which returns a MutableList, a list that can be modified and in this case is to store locally the mocked news for the adapter:

val news = mutableListOf<RedditNewsItem>()

Lists Functions & Lambdas

I created a method that I’ll be using it later to save the news when we are in the middle of an Orientation Change event but I would like to show you how easy is to work with list to filter and map (transform) every item from a list to another one.

In our code we have a method called “getNews” which returns a List of RedditNewsItems. In order to filter and transform our items (a list of ViewType) we are going to do this:

fun getNews(): List<RedditNewsItem> {
    return items
            .filter { it.getViewType() == AdapterConstants.NEWS }
            .map { it as RedditNewsItem }
}

Filter

Every list has some useful functions like “filter”, in this case, which allow us to iterate a list and filter (exclude) items that don’t apply certain condition. In our items list we have ViewTypes so we could have News items or Loading items, with this filter function we make sure to return only those that are News items.

Map

Another great function is “map” to transform the items from a list. In this case, we cast a ViewType object to a RedditNewsItem but we could also create and return new objects.

Lambdas

.map { it as RedditNewsItem }

Map is not another think than a function that receives as first parameter a function but Kotlin makes it great, because it allows you to exclude parenthesis and define a code block next to the function name, at the end this code block will be the first parameter required by the function and the code block is the famous Lambda expression, a function that is not declared but passed immediately as an expression

Here we are not going to go to more details about Lists and Lambdas but I think is really a good starting point to see this kind of examples on how to use this great features. Later in this tutorial we are going to take more time to talk about this.

Conclusion

I know that I spent more time explaining the Delegate Adapter pattern than Kotlin specific features but for sure I consider this pattern is really an excellent pattern which deserves to be presented with Kotlin code but I hope you had learned new Kotlin features with this new code. Also our code is getting better (or at list showing something in the UI, lol).

Don’t hesitate to contact me for any question, I’ll try to do my best to give you an answer. See you soon in next article !!!

 

Share your thoughts

2 COMMENTS

Loading Facebook Comments ...
Loading Disqus Comments ...