Reputation: 163
I have a recyclerView that shows a list of cart items, Every item is clickable and opens details fragment for that item, I'm Updating the item layout to have a delete button inside, the delete button suppose to call a method inside the fragment viewModel. I believe that making the viewModel a constructor in the adapter is not the best practice since separation of concern is important as I develop my skills.
I'm doing so with dataBinding and I've searched so much and couldn't find an answer.
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>
CartAdapter
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 delete function
fun deleteItemFromCart(makeupItem: MakeupItem) {
viewModelScope.launch {
Log.d(TAG, "DeleteItemFromCart method in viewModel called")
repository.deleteItemFromCart(makeupItem)
}
}
Upvotes: 0
Views: 538
Reputation: 163
I've came to an answer.
DataBinding of a viewModel with a recyclerView item won't work since we're not declaring the adapter in this viewModel, so you should make a callBack inside your adapter and receive it in your fragment then call your viewModel function.
Here is CartAdapter after modifying a callBack for your delete button onClick and use the same way for your 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)
}
}
As for CartListItem add two dataBinding one itemClickListener and the other for deleteButtonClickListener and use android:onClick and a lambda expression inside it
<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>
Once you get to your fragment and declaring your adapter pass the adapter parameters which will do a certain viewModel function or whatever you need to implement on every click of your cardItem and delete button
adapter = CartAdapter(CartAdapter.CartItemClickListener { makeupItem ->
viewModel.onMakeupItemClicked(makeupItem)
}, CartAdapter.DeleteItemClickListener { makeupItem ->
viewModel.deleteItemFromCart(makeupItem)
}
)
I hope this is the best practice answer and helps everyone has the same issue.
Upvotes: 1