Android Data Binding for RecyclerView With LiveData (Kotlin)
October 27, 2018Implement 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>()
}
}
- algo-trading
- algolia
- analytics
- android
- android-ktx
- android-permission
- android-studio
- apps-script
- bash
- binance
- bootstrap
- bootstrapvue
- chartjs
- chrome
- cloud-functions
- coding-interview
- contentresolver
- coroutines
- crashlytics
- crypto
- css
- dagger2
- datastore
- datetime
- docker
- eslint
- firebase
- firebase-auth
- firebase-hosting
- firestore
- firestore-security-rules
- flask
- fontawesome
- fresco
- git
- github
- glide
- godot
- google-app-engine
- google-cloud-storage
- google-colab
- google-drive
- google-maps
- google-places
- google-play
- google-sheets
- gradle
- html
- hugo
- inkscape
- java
- java-time
- javascript
- jetpack-compose
- jetson-nano
- kotlin
- kotlin-serialization
- layout
- lets-encrypt
- lifecycle
- linux
- logging
- lubuntu
- markdown
- mate
- material-design
- matplotlib
- md5
- mongodb
- moshi
- mplfinance
- mysql
- navigation
- nginx
- nodejs
- npm
- nuxtjs
- nvm
- pandas
- payment
- pip
- pwa
- pyenv
- python
- recylerview
- regex
- room
- rxjava
- scoped-storage
- selenium
- social-media
- ssh
- ssl
- static-site-generator
- static-website-hosting
- sublime-text
- ubuntu
- unit-test
- uwsgi
- viewmodel
- viewpager2
- virtualbox
- vue-chartjs
- vue-cli
- vue-router
- vuejs
- vuelidate
- vuepress
- web-development
- web-hosting
- webpack
- windows
- workmanager
- wsl
- yarn