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>() }}