Search code examples
androidkotlinmvvmandroid-recyclerviewviewmodel

How to call ViewModel.delete from RecyclerView Adapter?


I want to call my ViewModel.delete (Room Database) method from my Recyclerview Adapter but it's not working. Any ideas?

I want to call the ViewModel in OnBindViewHolder like: holder.binding.ivItemWalletDelete.setOnClickListener { WalletViewModel... }

but I get the error: on a null object

RecyclerView Adapter (WalletListAdapter.kt)

class WalletListAdapter:
        ListAdapter<Wallet, WalletListAdapter.ViewHolder>(WalletDiffCallback()){

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): ViewHolder {
        return ViewHolder.from(parent)
    }


    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = getItem(position)
        holder.bind(item)
    }

    class ViewHolder private constructor(val binding: ItemWalletsBinding) :
        RecyclerView.ViewHolder(binding.root) {

        fun bind(item: Wallet) {
            binding.wallet = item
            binding.executePendingBindings()
        }

        companion object {
            fun from(parent: ViewGroup): ViewHolder {
                val layoutInflater = LayoutInflater.from(parent.context)
                val binding = ItemWalletsBinding.inflate(layoutInflater, parent, false)
                return ViewHolder(binding)
            }
        }
    }
}

class WalletDiffCallback : DiffUtil.ItemCallback<Wallet>() {
    override fun areItemsTheSame(oldItem: Wallet, newItem: Wallet): Boolean {
        return oldItem.walletId == newItem.walletId
    }

    override fun areContentsTheSame(oldItem: Wallet, newItem: Wallet): Boolean {
        return oldItem == newItem
    }
}

ViewModel (WalletViewModel.kt)

class WalletViewModel(application: Application): AndroidViewModel(application) {
    private val repository = WalletRepository(application)
    private val liveWalletList = repository.getLiveDataWallets()

    fun insert(wallet: Wallet){
        viewModelScope.launch {
            repository.insert(wallet)
        }
    }

    fun update(wallet: Wallet){
        viewModelScope.launch {
            repository.update(wallet)
        }
    }

    fun delete(wallet: Wallet){
        viewModelScope.launch {
            repository.delete(wallet)
        }
    }

    fun getWalletById(walletId: Long): Wallet? {
        var wallet: Wallet? = null
        viewModelScope.launch {
            wallet = repository.getWalletById(walletId)
        }
        return wallet
    }

    fun getAllWallets(): List<Wallet>? {
        var wallets: List<Wallet>? = null
        viewModelScope.launch {
            wallets = repository.getAllWallets()
        }
        return wallets
    }

    fun getLiveWalletList(): LiveData<List<Wallet>> = liveWalletList
} 

Solution

  • A common way of tackling this kind of problem would be to create a callback that would be passed into the adapter.

    In your WalletListAdapter class pass in a callback that takes a Wallet object.

    class WalletListAdapter(private val onDeleteCallback: (Wallet) -> Unit) {}
    

    In your ViewHolder you can set an OnClickListener on each recycler view item and pass in the Wallet object to the callback function. That would look something like this:

    binding.root.setOnClickListener { onDeleteCallback(item) }
    

    Finally, the view (activity, fragment, etc) that initialized the WalletListAdapter would pass in a function that accepts a Wallet object. This is where you then call the WalletViewModel's delete function.

    class WalletListActivity: Activity() {
    
      @Inject
      lateinit var viewModel: WalletViewModel
    
      override fun onCreate(bundle: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // initialize the WalletListAdapter and pass in the callback
        val adapter = WalletListAdapter(::deleteWallet)
    
      }
    
      private fun deleteWallet(wallet: Wallet) {
        viewModel.delete(wallet)
      }
    
    }
    

    Note: You can first initialize the WalletListAdapter class with an empty constructor, then create a method to pass in the callback function from the view to the WalletListAdapter class. The point is to "propagate" the click event from the adapter back to the view, so the view can call the viewmodel's method.