Android Data Binding for RecyclerView With LiveData (Kotlin)

Implement LifecycleOwner for RecyclerView

Refer to Android Data Binding With LiveData (Kotlin) for the standard setup.

NOTE: Following tutorial on Android RecyclerView With Different Layout / View Type (Kotlin).

Sample code to load adapter and RecyclerView.

private fun setupUi() {    // list is RecyclerView    list.layoutManager = LinearLayoutManager(context)    // using a ViewModel    adapter = LocalListAdapter(viewModel)    list.adapter = adapter    val items = mutableListOf<ViewItem>().apply {        add(ViewItem.AlbumView("I am album"))        add(ViewItem.PinView("I am Pin"))    }    adapter.replaceItems(items)}

This example consist of a RecyclerView with 2 type of view: AlbumView which need Data Binding, while PinView doesn't.

sealed class ViewItem(val resource: Int) {    class AlbumView(val name: String): ViewItem(R.layout.albumview_list_item_album)    class PinView(val name: String): ViewItem(R.layout.albumview_list_item_pin)}

Adapter class.

For LiveData to work, we need to call binding.setLifecycleOwner but RecyclerView is not a LifecycleOwner. So we need to implement ViewHolder as a LifecycleOwner.

NOTE: If you are using observable, then LifecycleOwner implementation is not necessary.

private class LocalListAdapter(val viewModel: AlbumViewViewModel) : RecyclerView.Adapter<LocalListAdapter.ViewHolder>() {    private var items: List<ViewItem> = listOf()    override fun getItemCount(): Int {        return items.size    }    override fun getItemViewType(position: Int): Int {        return items[position].resource    }    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {        return if (viewType == R.layout.albumview_list_item_album) {            val inflater = LayoutInflater.from(parent.context)            val binding = AlbumviewListItemAlbumBinding.inflate(inflater, parent, false)            // val binding = DataBindingUtil.inflate<AlbumviewListItemAlbumBinding>(inflater, R.layout.albumview_list_item_album, parent, false)            val viewHolder = BindingViewHolder(binding.root, binding)            // suspected cannot mix observable with LiveData with Observable            binding.setLifecycleOwner(viewHolder)            return viewHolder        } else {            val view = LayoutInflater.from(parent.context)                    .inflate(viewType, parent, false)            ViewHolder(view)        }    }    override fun onBindViewHolder(holder: ViewHolder, position: Int) {        val item = items[position]        val context = holder.containerView.context        when (item) {            is ViewItem.AlbumView -> {                if (holder is BindingViewHolder) {                    holder.apply {                        viewModel.liveItem.name.value = item.name                        binding.viewModel = viewModel                        // optional observer to update name if it changes (2-way binding through EditText)                        viewModel.liveItem.name.observe(this, Observer {                            it?.also {                                item.name = it                            }                        })                    }                }            }            is ViewItem.PinView -> {                // do something            }        }    }    override fun onViewAttachedToWindow(holder: ViewHolder) {        super.onViewAttachedToWindow(holder)        if (holder is BindingViewHolder) {            holder.markAttach()        }    }    override fun onViewDetachedFromWindow(holder: ViewHolder) {        super.onViewDetachedFromWindow(holder)        if (holder is BindingViewHolder) {            holder.markDetach()        }    }    fun replaceItems(items: List<ViewItem>) {        this.items = items        notifyDataSetChanged()    }    open inner class ViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView), LayoutContainer    inner class BindingViewHolder(override val containerView: View, val binding: AlbumviewListItemAlbumBinding) : ViewHolder(containerView), LifecycleOwner {        private val lifecycleRegistry = LifecycleRegistry(this)        init {            lifecycleRegistry.markState(Lifecycle.State.INITIALIZED)        }        fun markAttach() {            // Lifecycle.State.CREATED doesn't work for this case            // lifecycleRegistry.markState(Lifecycle.State.CREATED)            lifecycleRegistry.markState(Lifecycle.State.STARTED)            // lifecycleRegistry.markState(Lifecycle.State.RESUMED)        }        fun markDetach() {            lifecycleRegistry.markState(Lifecycle.State.DESTROYED)        }        override fun getLifecycle(): Lifecycle {            return lifecycleRegistry        }    }}

Layout file albumview_list_item_album.xml will generate AlbumviewListItemAlbumBinding.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="viewModel" type="com.luasoftware.pixpin.view.AlbumViewViewModel"/>
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        >

        <EditText
            android:id="@+id/albumNameEditText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@={viewModel.liveItem.name}"
            />

        <TextView
            android:id="@+id/albumNameTextView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{viewModel.liveItem.name}"
            />
    </LinearLayout>
</layout>

ViewModel

// I remove my dagger2 injection code// class AlbumViewViewModel @Inject constructor(private val dataSource: AlbumDao): ViewModel() {class AlbumViewViewModel : ViewModel() {    inner class LiveItem {        val name = MutableLiveData<String>()    }}

❤️ Is this article helpful?

Buy me a coffee ☕ or support my work via PayPal to keep this space 🖖 and ad-free.

Do send some 💖 to @d_luaz or share this article.

✨ By Desmond Lua

A dream boy who enjoys making apps, travelling and making youtube videos. Follow me on @d_luaz

👶 Apps I built

Travelopy - discover travel places in Malaysia, Singapore, Taiwan, Japan.