RoberV
RoberV

Reputation: 586

RecyclerView item not updated with LiveData

I have an application using databinding, livedata, room, kotlin koroutines, viewmodel, navigation component and dagger. I have one activity, and two fragments.

ListFragment: Show in a recyclerview a list of items.

DetalFragment: Show the item detail, and can update some fields of the item with a save button.

The problem is when I update some fields from detailfragment, then the changes isn´t visibles in the listfragment, but when I scroll down and up, the changes become visible.

ListFragment:

 @Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
val viewModel: ListViewModel by viewModels {
    viewModelFactory
}

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    val bindings = ListFragmentBinding.inflate(inflater, container, false).apply {
        viewmodel = viewModel
    }
    bindings.lifecycleOwner = this
    adapter = ItemsAdapter()
    bindings.recyclerView.adapter = adapter
    viewModel.items.observe(
        viewLifecycleOwner,
        Observer { adapter.submitList(it)})
    return bindings.root
}

ListViewModel:

var items: LiveData<PagedList<Item>> = repository.items

Repository:

val items<PagedList<Item>>
get()=itemDao.getAllItemsPaged().toLiveData(pageSize=50)
fun getItemFlow(id: String): Flow<Item> = itemDao.getItemFlow(id)
suspend fun updateItem(item: Item) {
   itemDao.updateItem(item)
}

ItemDao:

 @Query("SELECT * FROM item")
fun getAllItemsPaged(): DataSource.Factory<Int,Item>
@Query("SELECT * FROM itemWHERE id=:id")
fun getItemFlow(id:String):Flow<Item>
@Update
suspend fun updateItem(item:Item)

ItemFragment:

@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
val viewModel: ItemViewModel by viewModels {
    viewModelFactory
}
override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    viewModel.loadItem(args.itemId)
    val bindings = ItemFragmentBinding.inflate(inflater, container, false).apply {
        viewmodel = viewModel
        buttonSave.setOnClickListener{viewModel. viewModelScope.launch {
        viewModel.saveItem()
        findNavController().navigateUp()
    }}

    }

    bindings.lifecycleOwner = this
    return bindings.root
}

ItemViewModel:

var item: LiveData<Item>? = null

fun loadItem(id: String) {
    viewModelScope.launch {
        item = repository.getItemFlow(id).asLiveData()
    }
}

suspend fun saveItem() {
    item!!.value!!.someField = "hi"
    repository.updateItem(item!!.value!!)
}

Upvotes: 3

Views: 3279

Answers (2)

RoberV
RoberV

Reputation: 586

The problem is in the items adapter. The adapter needs a correct implementation of the DiffUtil.ItemCallback.

In this case:

private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Contador>() {
    // The ID property identifies when items are the same.
    override fun areItemsTheSame(oldItem: Contador, newItem: Contador) =
        oldItem.id == newItem.id

    // Check the properties that can change, or implements the equals method in Item class
    override fun areContentsTheSame(
        oldItem: Item, newItem: Item) = oldItem.someField == newItem.someField
}

Upvotes: 1

riccardogabellone
riccardogabellone

Reputation: 288

I can confirm that DiffUtil is the way. In case of a generic approach over a RecyclerView:

ItemListDiffUtil.kt

open class ItemListDiffUtil<T>(private val oldItems: ArrayList<T>, private val newItems: ArrayList<T>) : DiffUtil.Callback(){
    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldItems[oldItemPosition] == newItems[newItemPosition]
    }

    override fun getOldListSize(): Int {
        return oldItems.size
    }

    override fun getNewListSize(): Int {
        return newItems.size
    }

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldItems[oldItemPosition] == newItems[newItemPosition]
    }
}

YOUR_OBJECTAdapter.kt

class YOUR_OBJECTAdapter() : RecyclerView.Adapter<YOUR_OBJECTAdapter.MyViewHolder>() {

    class MyViewHolder(viewItem: View) : RecyclerView.ViewHolder(viewItem) {
        //your code
    }

    private var myDataset = ArrayList<YOUR_OBJECT>()

    fun setDataset(data : ArrayList<YOUR_OBJECT>){

        /*diffUtil*/
        val diffCallback = ItemListDiffUtil(myDataset,data)
        val diffResult = DiffUtil.calculateDiff(diffCallback)
        diffResult.dispatchUpdatesTo(this)

        myDataset.clear()
        myDataset.addAll(data)
    }


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val viewItem = LayoutInflater.from(parent.context).inflate(R.layout.YOUR_OBJECT, parent, false)
        return MyViewHolder(viewItem)
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        //your code
    }

    override fun getItemCount(): Int {
        return myDataset.size
    }
}

Upvotes: 0

Related Questions