In my code I'm using two view models: one for the list of items used in a RecyclerView and one for the item itself, which I am binding to an item detail view. Once the view is created, I'm having issues making any updates from the item detail view model to the view. So in the code below, when the button is clicked, the onButtonClick() function in the ItemViewModel is called but text update in the item_card view does not update.
Any ideas greatly appreciated.
Thanks!
MainActivity.kt:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var itemListVM: ItemListViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.lifecycleOwner = this
setupBindings()
}
private fun setupBindings() {
binding.lifecycleOwner = this
// Create VM
itemListVM = ViewModelProviders.of(this).get(ItemListViewModel::class.java)
binding.itemListVM = itemListVM
setupRecyclerView()
}
// Set up RecyclerView
private fun setupRecyclerView() {
val recyclerView = binding.itemListRecyclerview
val layoutManager = LinearLayoutManager(this)
recyclerView.layoutManager = layoutManager
// Set adapter
recyclerView.adapter = itemListVM.getAdapter()
// Create some data
var itemList = mutableListOf<ItemModel>()
for (i in 0..5){
var item = ItemModel()
itemList.add(item)
}
itemListVM.setItemList(itemList)
}
ItemModel.kt:
class ItemModel{
var text = ""
}
ItemViewModel.kt:
class ItemViewModel: ViewModel(){
var text = MutableLiveData<String>()
lateinit var itemModel : ItemModel
var position : Int = -1
init{
text.value = "init"
}
fun setItem(itemModel: ItemModel){
this.itemModel = itemModel
text.value = "set model"
}
fun onButtonClick(){
Log.d("ItemViewModel", "position: $position")
text.value = "button click"
}
}
ItemListViewModel.kt:
class ItemListViewModel: ViewModel(){
var itemVMMap = mutableMapOf<Int, ItemViewModel>()
var itemListAdapter : ItemListAdapter? = null
init {
itemListAdapter = ItemListAdapter(this)
}
fun getAdapter(): ItemListAdapter? {
return itemListAdapter
}
fun setItemList(itemList: List<ItemModel>){
itemListAdapter?.setItemList(itemList)
}
fun getItemAt(position: Int?): ItemModel? {
val itemList = itemListAdapter?.getItemList()
if (itemList != null &&
position != null &&
itemList.size > position){
return itemList.get(position)
}
else {
return null
}
}
fun getItemVMAtPosition(position: Int?):ItemViewModel?{
// Check if VM exists
var itemVM: ItemViewModel? = null
if (itemVMMap.contains(position)){
itemVM = itemVMMap[position]
}
else {
// Create a new VM
itemVM = ItemViewModel()
itemVM.position = position!!
// Get item
val item = getItemAt(position)
item?.let{
itemVM?.setItem(it)
}
itemVMMap[position!!] = itemVM
}
return itemVM
}
}
ItemListAdapter.kt:
class ItemListAdapter
internal constructor(listViewModel : ItemListViewModel) : RecyclerView.Adapter<ItemListAdapter.GenericViewHolder>() {
private var itemList: List<ItemModel>? = null
private var itemListVM: ItemListViewModel? = null
private var layoutId: Int = 0
init {
this.layoutId = layoutId
this.itemListVM = listViewModel
}
private fun getLayoutIdForPosition(position: Int): Int {
return layoutId
}
override fun getItemCount(): Int {
return itemList?.size ?: 0
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GenericViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = DataBindingUtil.inflate<ViewDataBinding>(
layoutInflater,
com.example.test2.R.layout.item_card,
parent,
false
) as com.example.test2.databinding.ItemCardBinding
return GenericViewHolder(binding)
}
override fun onBindViewHolder(holder: GenericViewHolder, position: Int) {
if (itemListVM != null) {
val itemVM = itemListVM!!.getItemVMAtPosition(position)
holder.bind(itemVM!!, position)
}
}
override fun getItemViewType(position: Int): Int {
return getLayoutIdForPosition(position)
}
// View Holder Definition
inner class GenericViewHolder(val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(viewModel: ItemViewModel, position: Int?) {
binding.setVariable(BR.itemListVM, itemListVM)
binding.setVariable(BR.position, position)
binding.setVariable(BR.itemVM, viewModel)
binding.executePendingBindings()
}
}
// Setters
fun setItemList(itemList: List<ItemModel>?) {
this.itemList = itemList
notifyDataSetChanged()
}
// Getters
fun getItemList(): List<ItemModel>? {
return itemList
}
}
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable name="itemListVM" type="com.example.test2.ItemListViewModel"/>
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/item_list_recyclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical">
</androidx.recyclerview.widget.RecyclerView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>
item_card.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View" />
<variable name="itemListVM" type="com.example.test2.ItemListViewModel"/>
<variable name="position" type="java.lang.Integer" />
<variable name="itemVM" type="com.example.test2.ItemViewModel"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_margin="12dp">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{itemVM.text}"/>
<Button android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:paddingLeft="12dp"
android:onClick="@{() -> itemVM.onButtonClick()}"
android:text="Click Me"/>
</LinearLayout>
</layout>
This is just because you are using a mutable property, but not notifying your view somehow that your value is being updated. for binding them directly you should make it an observable and then attach it with view in databinding or you should notify the updates you are doing on that variable.
public class Foo {
public final ObservableField<String> baz = new ObservableField<>();
}
however on view side do this,
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{foo.baz}"/>
and while changing the value you can do it like that -
baz.set("changed value") and it will automatically update your view.