Привязка данных в элементе recyclerView не работает с фрагментом viewModel

У меня есть recyclerView, который показывает список элементов корзины. Каждый элемент доступен для клика и открывает фрагмент сведений для этого элемента. Я обновляю макет элемента, чтобы иметь кнопку удаления внутри, кнопка удаления предполагает вызов метода внутри фрагмента viewModel . Я считаю, что создание viewModel в качестве конструктора в адаптере — не лучшая практика, так как разделение ответственности важно, поскольку я развиваю свои навыки.

Я делаю это с помощью dataBinding, и я так много искал и не нашел ответа.

CartListItem.xml

<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 = "makeupItem"
        type = "com.melfouly.makeupshop.model.MakeupItem" />

    <variable
        name = "viewModel"
        type = "com.melfouly.makeupshop.makeupcart.CartViewModel" />

</data>

<com.google.android.material.card.MaterialCardView
    android:id = "@+id/cart_card_item"
    android:layout_width = "match_parent"
    android:layout_height = "100dp"
    android:layout_margin = "4dp"
    android:backgroundTint = "@color/primary"
    app:cardCornerRadius = "8dp"
    app:cardElevation = "8dp">

    <LinearLayout
        android:layout_width = "match_parent"
        android:layout_height = "match_parent"
        android:weightSum = "6">

        <ImageView
            android:id = "@+id/item_image"
            loadImage = "@{makeupItem}"
            android:layout_gravity = "center"
            android:layout_width = "0dp"
            android:layout_height = "match_parent"
            android:layout_weight = "1"
            android:scaleType = "fitCenter"
            tools:src = "@tools:sample/avatars" />

        <TextView
            android:id = "@+id/item_name"
            android:layout_width = "0dp"
            android:layout_height = "match_parent"
            android:layout_weight = "2"
            android:fontFamily = "@font/aclonica"
            android:gravity = "center"
            android:text = "@{makeupItem.name}"
            android:textColor = "@color/black"
            android:textStyle = "bold"
            tools:text = "Item name" />


        <TextView
            android:id = "@+id/item_price"
            loadPrice = "@{makeupItem}"
            android:layout_width = "0dp"
            android:layout_height = "match_parent"
            android:layout_weight = "2"
            android:fontFamily = "@font/aclonica"
            android:gravity = "center"
            android:textColor = "@color/black"
            tools:text = "5$" />

        <Button
            android:id = "@+id/delete_button"
            android:layout_width = "0dp"
            android:layout_height = "match_parent"
            android:layout_weight = "1"
            android:onClick = "@{() -> viewModel.deleteItemFromCart(makeupItem)}"
            app:icon = "@drawable/ic_baseline_delete_outline_24"
            app:iconGravity = "end" />


    </LinearLayout>

</com.google.android.material.card.MaterialCardView>
</layout>

КорзинаАдаптер

class CartAdapter(private val clickListener: (MakeupItem) -> Unit) :
ListAdapter<MakeupItem, CartAdapter.CartViewHolder>(DiffCallback()) {

class CartViewHolder(private val binding: CartListItemBinding) :
    RecyclerView.ViewHolder(binding.root) {
    fun bind(makeupItem: MakeupItem) {
        binding.makeupItem = makeupItem
        binding.executePendingBindings()
    }
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CartViewHolder {
    val layoutInflater = LayoutInflater.from(parent.context)
    val binding = CartListItemBinding.inflate(layoutInflater, parent, false)
    return CartViewHolder(binding)
}

override fun onBindViewHolder(holder: CartViewHolder, position: Int) {
    val makeupItem = getItem(position)
    holder.itemView.setOnClickListener {
        clickListener(makeupItem)
    }
    holder.bind(makeupItem)
}

class DiffCallback : DiffUtil.ItemCallback<MakeupItem>() {
    override fun areItemsTheSame(oldItem: MakeupItem, newItem: MakeupItem): Boolean {
        return oldItem.id == newItem.id
    }

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

Функция удаления CartViewModel

fun deleteItemFromCart(makeupItem: MakeupItem) {
    viewModelScope.launch {
        Log.d(TAG, "DeleteItemFromCart method in viewModel called")
        repository.deleteItemFromCart(makeupItem)
    }
}
0
0
77
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я пришел к ответу.

DataBinding viewModel с элементом recyclerView не будет работать, поскольку мы не объявляем адаптер в этой модели представления, поэтому вы должны сделать callBack внутри вашего адаптера и получить его в своем фрагменте, а затем вызвать функцию viewModel.

Вот CartAdapter после изменения callBack для вашей кнопки удаления onClick и используйте тот же способ для вашего cardItem

class CartAdapter(
private val cartItemClickListener: CartItemClickListener,
private val deleteItemClickListener: DeleteItemClickListener
) :
ListAdapter<MakeupItem, CartAdapter.CartViewHolder>(DiffCallback()) {

class CartViewHolder(private val binding: CartListItemBinding) :
    RecyclerView.ViewHolder(binding.root) {
    fun bind(
        makeupItem: MakeupItem,
        cartItemClickListener: CartItemClickListener,
        deleteItemClickListener: DeleteItemClickListener
    ) {
        binding.makeupItem = makeupItem
        binding.cartItemClickListener = cartItemClickListener
        binding.deleteItemClickListener = deleteItemClickListener
        binding.executePendingBindings()
    }
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CartViewHolder {
    val layoutInflater = LayoutInflater.from(parent.context)
    val binding = CartListItemBinding.inflate(layoutInflater, parent, false)
    return CartViewHolder(binding)
}

override fun onBindViewHolder(holder: CartViewHolder, position: Int) {
    val makeupItem = getItem(position)
    holder.bind(makeupItem, cartItemClickListener, deleteItemClickListener)
}

class DiffCallback : DiffUtil.ItemCallback<MakeupItem>() {
    override fun areItemsTheSame(oldItem: MakeupItem, newItem: MakeupItem): Boolean {
        return oldItem.id == newItem.id
    }

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

class CartItemClickListener(val clickListener: (makeupItem: MakeupItem) -> Unit) {
    fun onClick(makeupItem: MakeupItem) = clickListener(makeupItem)
}

class DeleteItemClickListener(val deleteItemClickListener: (makeupItem: MakeupItem) -> Unit) {
    fun onClick(makeupItem: MakeupItem) = deleteItemClickListener(makeupItem)
}

}

Что касается CartListItem, добавьте два dataBinding, один itemClickListener, а другой для deleteButtonClickListener, и используйте android:onClick и лямбда-выражение внутри него.

<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 = "makeupItem"
        type = "com.melfouly.makeupshop.model.MakeupItem" />

    <variable
        name = "cartItemClickListener"
        type = "com.melfouly.makeupshop.makeupcart.CartAdapter.CartItemClickListener" />

    <variable
        name = "deleteItemClickListener"
        type = "com.melfouly.makeupshop.makeupcart.CartAdapter.DeleteItemClickListener" />

</data>

<com.google.android.material.card.MaterialCardView
    android:id = "@+id/cart_card_item"
    android:layout_width = "match_parent"
    android:layout_height = "100dp"
    android:layout_margin = "4dp"
    android:backgroundTint = "@color/primary"
    android:onClick = "@{() -> cartItemClickListener.onClick(makeupItem)}"
    app:cardCornerRadius = "8dp"
    app:cardElevation = "8dp">

    <LinearLayout
        android:layout_width = "match_parent"
        android:layout_height = "match_parent"
        android:weightSum = "6">

        <ImageView
            android:id = "@+id/item_image"
            loadImage = "@{makeupItem}"
            android:layout_width = "0dp"
            android:layout_height = "match_parent"
            android:layout_gravity = "center"
            android:layout_weight = "1"
            android:scaleType = "fitCenter"
            tools:src = "@tools:sample/avatars" />

        <TextView
            android:id = "@+id/item_name"
            android:layout_width = "0dp"
            android:layout_height = "match_parent"
            android:layout_weight = "2"
            android:fontFamily = "@font/aclonica"
            android:gravity = "center"
            android:text = "@{makeupItem.name}"
            android:textColor = "@color/black"
            android:textStyle = "bold"
            tools:text = "Item name" />


        <TextView
            android:id = "@+id/item_price"
            loadPrice = "@{makeupItem}"
            android:layout_width = "0dp"
            android:layout_height = "match_parent"
            android:layout_weight = "2"
            android:fontFamily = "@font/aclonica"
            android:gravity = "center"
            android:textColor = "@color/black"
            tools:text = "5$" />

        <Button
            android:id = "@+id/delete_button"
            android:layout_width = "0dp"
            android:layout_height = "match_parent"
            android:layout_weight = "1"
            android:onClick = "@{() -> deleteItemClickListener.onClick(makeupItem)}"
            app:icon = "@drawable/ic_baseline_delete_outline_24"
            app:iconGravity = "end" />


    </LinearLayout>

</com.google.android.material.card.MaterialCardView>
</layout>

Как только вы доберетесь до своего фрагмента и объявите свой адаптер, передайте параметры адаптера, которые будут выполнять определенную функцию viewModel или все, что вам нужно реализовать при каждом нажатии кнопки вашего cardItem и удаления

adapter = CartAdapter(CartAdapter.CartItemClickListener { makeupItem ->
        viewModel.onMakeupItemClicked(makeupItem)
    }, CartAdapter.DeleteItemClickListener { makeupItem ->
        viewModel.deleteItemFromCart(makeupItem)
    }
    )

Я надеюсь, что это лучший практический ответ, который поможет всем, у кого есть такая же проблема.

Другие вопросы по теме