Binding data inside an Adapter Kotlin - android

I have created the adapter below which shows two different data models in the recycler view.
However, I am not sure how to do the bindings in the bind functions written in the ViewHolders. I have two seperate xml files which I would like to bind when this "bind" function is called but how do I set the data?
My code is as follows:
class HomeAdapter(
private val context: Context
) :
RecyclerView.Adapter<HomeAdapter.BaseViewHolder<*>>() {
private var homeList: List<Any> = emptyList()
companion object {
private const val TYPE_VISIT = 0
private const val TYPE_WASH = 1
}
abstract class BaseViewHolder<T>(itemView: View) : RecyclerView.ViewHolder(itemView) {
abstract fun bind(item: T)
}
inner class VisitViewHolder(itemView: View) : BaseViewHolder<HomeVisitLabel>(itemView) {
override fun bind(item: HomeVisitLabel) {
//Do your view assignment here from the data model
}
}
inner class WashViewHolder(itemView: View) : BaseViewHolder<HomeWashLabel>(itemView) {
override fun bind(item: HomeWashLabel) {
//Do your view assignment here from the data model
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<*> {
return when (viewType) {
TYPE_VISIT -> {
val view = LayoutInflater.from(context)
.inflate(R.layout.reward_label_visit_card, parent, false)
VisitViewHolder(view)
}
TYPE_WASH -> {
val view = LayoutInflater.from(context)
.inflate(R.layout.reward_label_wash_card, parent, false)
WashViewHolder(view)
}
else -> throw IllegalArgumentException("Invalid view type")
}
}
override fun onBindViewHolder(holder: BaseViewHolder<*>, position: Int) {
val element = homeList[position]
when (holder) {
is VisitViewHolder -> holder.bind(element as HomeVisitLabel)
is WashViewHolder -> holder.bind(element as HomeWashLabel)
else -> throw IllegalArgumentException()
}
}
override fun getItemViewType(position: Int): Int {
val comparable = homeList[position]
return when (comparable) {
is HomeVisitLabel -> TYPE_VISIT
is HomeWashLabel -> TYPE_WASH
else -> throw IllegalArgumentException("Invalid type of data " + position)
}
}
override fun getItemCount(): Int {
return homeList.size
}
}
One of the two XML files
<?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>
<variable
name="VisitLabel"
type="com.modelz.HomeVisitLabel" />
</data>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="5dp"
android:layout_margin="5dp"
app:cardBackgroundColor="#color/colorPrimary">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginLeft="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:text="Visit:"
android:gravity="left"
android:layout_weight="1"
android:layout_marginTop="5dp"
android:textStyle="bold"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:text="#{VisitLabel.Name}"
android:gravity="left"
android:layout_weight="1"
android:layout_marginTop="5dp"
android:textStyle="bold"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Redeem"
android:visibility="invisible"/>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_weight="0.5"
android:gravity="left"
android:text="#{VisitLabel.descript}"
android:textSize="16dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_weight="0.5"
android:gravity="left"
android:text="Progress:"
android:textSize="16dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_weight="0.9"
android:text="#{VisitLabel.countUser}"
android:gravity="right"
android:textSize="16sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_weight="0.5"
android:text="#{VisitLabel.countSet}"
android:gravity="left"
android:textSize="16sp" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
</layout>

if you want to use data binding, you need to inflate your layouts from Binding class as follow:
val binding = RewardLabelVisitCardBinding.inflate(layoutInflater, parent, false)
and same for the other layout.
You can get layoutInflator using:
val layoutInflater = LayoutInflater.from(parent.context)
And then in the bind function you need to use that binding variable to assign attributes. like,
binding.textView.text = item.name
The overall code for better understanding is given below:
class VisitViewHolder(private val binding: RewardLabelVisitCardBinding) : BaseViewHolder<HomeVisitLabel>(binding.root) {
override fun bind(item: HomeVisitLabel) {
binding.VisitLabel = item
binding.executePendingBindings()
}
}
Just pass binding variable to ViewHolder after inflating the layout in onCreateViewHolder
return VisitViewHolder(binding)
Hope, that answers your question!

You will need to complete the bind functions for both the viewholders:
override fun bind(item: HomeVisitLabel) {
// textView.text = item.name // example
}
You should be able to assign the values in the function above as demonstrated by the example, this should work as in onBindViewHolder, the bind function is called depending on the type of data at the position in the array, allowing the data to be bound to different xml layout files.
You will have to complete the bind functions for both viewholders, setting text views to item data strings for example or setting images resources for image views, corresponding to the correct xml layout file for the data type.

Related

Databinding in recyclerView item doesn't work with the fragment viewModel

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)
}
}
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.

Recycler view is not scrolling smoothly - Cardview

Hi I am using a recycler view with a card view, I have no idea why it is not scrolling smoothly. it stuck for a few seconds and shows the next content.
This is my xml for list item
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
android:id="#+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:clickable="true"
android:focusable="true"
app:cardBackgroundColor="#color/colorAccent"
app:cardCornerRadius="10dp"
app:cardElevation="10dp" >
<LinearLayout
android:id="#+id/product_list_item_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="#+id/product_name_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="name"
android:textColor="#FFFFFF"
android:textSize="30sp"
android:textStyle="bold" />
<TextView
android:id="#+id/product_catagory_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="catagory"
android:textColor="#FFFFFF"
android:textSize="24sp"
android:textStyle="bold" />
<TextView
android:id="#+id/product_end_date_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="End Date"
android:textColor="#FFFFFF"
android:textSize="24sp"
android:textStyle="bold" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
this is my recyclerViewAdapter
class ProductsRecyclerViewAdapter(private val clickListener: (Product) -> Unit): RecyclerView.Adapter<ProductsMyViewHolder>() {
private val productsList = ArrayList<Product>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductsMyViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding: ProductsListItemBinding =
DataBindingUtil.inflate(layoutInflater, R.layout.products_list_item, parent, false)
return ProductsMyViewHolder(binding)
}
override fun getItemCount(): Int {
return productsList.size
}
override fun onBindViewHolder(holder: ProductsMyViewHolder, position: Int) {
holder.bind(productsList[position], clickListener)
}
fun setList(products: List<Product>) {
productsList.clear()
productsList.addAll(products)
}
}
class ProductsMyViewHolder(val binding: ProductsListItemBinding): RecyclerView.ViewHolder(binding.root) {
fun bind(product: Product, clickListener: (Product) -> Unit) {
binding.productNameTextView.text = product.name
binding.productCatagoryTextView.text = product.catagory
binding.productEndDateTextView.text = convertLongToTime(product.end_date)
binding.productListItemLayout.setOnClickListener {
clickListener(product)
}
}
fun convertLongToTime(time: Long): String {
val date = Date(time)
val format = SimpleDateFormat("dd MMM yyyy")
return format.format(date)
}
}
This is my fragment where i initialise the ProductsRecyclerViewAdapter
private fun initRecyclerView() {
binding.productsRecyclerView.layoutManager = LinearLayoutManager(context)
adapter = ProductsRecyclerViewAdapter ({ selectedItem: Product -> listItemClicked(selectedItem)})
binding.productsRecyclerView.adapter = adapter
displayProductssList()
}
private fun displayProductssList() {
productsViewModel.products.observe(viewLifecycleOwner, Observer {
Log.i("MYTAG", it.toString())
adapter.setList(it)
adapter.notifyDataSetChanged()
})
}
any thoughts what I might be doing wrong here
your suggestion would be very helpful
thanks in advance
R
The only thing I see in your code is creating new SimpleDateFormat on every bind, it is definitely not free. Create single instance in adapter.
Also, maybe your products observer executes too often?

Display cards dynamically in android studio

Heyy, it's the first time i use android studio and i want to display the content of a class in a card view dynamically on my page. For exemple I have a class Book that contains the Book title and a little description and i initialise 3 instances of books.. I want 3 cards in my page containing the books titles and description. How can this be done ? Thankz
You should create a recycleview.
They are the steps }
First. Import the dependecy
dependencies {
implementation 'androidx.recyclerview:recyclerview:1.0.0'
}
Second. Add RecyclerView in xml
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
Third. Create the adapter view
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="4dp">
<ImageView android:id="#+id/foto"
android:layout_width="?android:attr/listPreferredItemHeight"
android:layout_height="?android:attr/listPreferredItemHeight"
android:contentDescription="fotografía"
android:src="#drawable/bar"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"/>
<TextView android:id="#+id/nombre"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Nombres del lugar"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textStyle="bold"
android:maxLines="1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="#+id/foto"
app:layout_constraintEnd_toEndOf="parent"/>
<TextView android:id="#+id/direccion"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:maxLines="1"
android:text="dirección del lugar"
app:layout_constraintTop_toBottomOf="#id/nombre"
app:layout_constraintStart_toEndOf="#+id/foto"
app:layout_constraintEnd_toEndOf="parent"/>
<RatingBar android:id="#+id/valoracion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:attr/ratingBarStyleSmall"
android:isIndicator="true"
android:rating="3"
app:layout_constraintTop_toBottomOf="#id/direccion"
app:layout_constraintLeft_toRightOf="#+id/foto"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Fourth. Create the adpater's class
class AdaptadorLugares(private val lugares: RepositorioLugares) :
RecyclerView.Adapter<AdaptadorLugares.ViewHolder>() {
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
fun personaliza(lugar: Lugar) = with(itemView){
nombre.text = lugar.nombre
direccion.text = lugar.direccion
foto.setImageResource(when (lugar.tipoLugar) {
TipoLugar.RESTAURANTE -> R.drawable.restaurante
TipoLugar.BAR -> R.drawable.bar
TipoLugar.COPAS -> R.drawable.copas
TipoLugar.ESPECTACULO -> R.drawable.espectaculos
TipoLugar.HOTEL -> R.drawable.hotel
TipoLugar.COMPRAS -> R.drawable.compras
TipoLugar.EDUCACION -> R.drawable.educacion
TipoLugar.DEPORTE -> R.drawable.deporte
TipoLugar.NATURALEZA -> R.drawable.naturaleza
TipoLugar.GASOLINERA -> R.drawable.gasolinera
TipoLugar.OTROS -> R.drawable.otros
})
foto.setScaleType(ImageView.ScaleType.FIT_END)
valoracion.rating = lugar.valoracion
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
ViewHolder {
val v = LayoutInflater.from(parent.context)
.inflate(R.layout.elemento_lista, parent, false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ViewHolder, posicion: Int) {
val lugar = lugares.elemento(posicion)
holder.personaliza(lugar)
}
override fun getItemCount() = lugares.tamanyo()
}
Finally call the adapter in activity's main
You can check the oficial documentation in google
https://developer.android.com/guide/topics/ui/layout/recyclerview
Example code
http://www.androidcurso.com/index.php/691

Recycler view strange behaviour when height is match_parent

I am facing an strange behaviour now days. I am using view binding for refrencing ids. When RecyclerView height is wrap_content the item display perfectly but when I set recyclerview height match_parent the item width is automatically wrapped (wrap_content) in run time but in xml I have set item width is match_parent.
Please help me to solve this problem.
Please look at my xml and kt files -:
<LinearLayou ------
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/idRvTrainerReview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
row_item
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/_1sdp"
android:background="#color/colorWhite"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="#dimen/_10sdp">
<TextView
android:id="#+id/idTvDate"
style="#style/MediumText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="14 feb"
android:includeFontPadding="false"
android:textColor="#color/colorBlack"
android:textStyle="bold" />
<TextView
android:id="#+id/idTvStartTime"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.2"
android:gravity="center"
android:includeFontPadding="false"
android:text="19:30" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_right_arrow" />
</LinearLayout>
adapter file -:
class FeedbackDataAdapter(
private var mcontext: Context,
private var mItemList: MutableList<ResponseTrainerFeedback.Result.Training>,
var mClickListener: OnClickListener?
) : RecyclerView.Adapter<TrainingDataViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrainingDataViewHolder {
var layoutInflater = mcontext.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as (LayoutInflater)
val rowEventMemberBinding =
RowEventDataBinding.inflate(layoutInflater, parent, false)
return TrainingDataViewHolder(rowEventMemberBinding)
}
override fun getItemCount(): Int = mItemList.size
override fun onBindViewHolder(holder: TrainingDataViewHolder, position: Int) {
holder.mBinding.idTvDate.text = mItemList[holder.adapterPosition].trainingName
holder.mBinding.idTvStartTime.invisible()
holder.itemView.setOnClickListener {
if(mItemList.size > 0){
mClickListener?.onItemClick(holder.adapterPosition)
}
}
}
fun refreshList(mFeedbackList: MutableList<ResponseTrainerFeedback.Result.Training>) {
this.mItemList = mFeedbackList
notifyDataSetChanged()
}
}
class TrainingDataViewHolder(var mBinding: RowEventDataBinding) :
RecyclerView.ViewHolder(mBinding.root) {
}
I've tried your code and It's working.
I've only refactored your adapter according to Android training and if you want to do the codelab: https://codelabs.developers.google.com/codelabs/kotlin-android-training-diffutil-databinding
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrainingDataViewHolder {
return TrainingDataViewHolder.from(parent)
}
override fun getItemCount(): Int = mItemList.size
override fun onBindViewHolder(holder: TrainingDataViewHolder, position: Int) {
holder.bind(mItemList[position])
holder.itemView.setOnClickListener {
if(mItemList.size > 0){
mClickListener?.onItemClick(holder.adapterPosition)
}
}
}
fun refreshList(mFeedbackList: MutableList<ResponseTrainerFeedback.Result.Training>) {
this.mItemList = mFeedbackList
notifyDataSetChanged()
}
}
class TrainingDataViewHolder(var mBinding: RowEventDataBinding) :
RecyclerView.ViewHolder(mBinding.root) {
fun bind(data: YOURDATA) {
mBinding.idTv = data // it'll map automatically all the property, replace idTv by the name of your variable in your layout
mBinding.idTvStartTime.invisible()
}
companion object {
fun from(parent: ViewGroup): TrainingDataViewHolder {
return TrainingDataViewHolder(RowEventDataBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
}
}
row_item
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="idTv"
type="package.name.to.data" />
</data>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="1dp"
android:background="#android:color/white"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="10dp">
<TextView
android:id="#+id/idTvDate"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.8"
android:text="#{idTv.idTvDate}"
android:includeFontPadding="false"
android:textColor="#android:color/black"
android:textStyle="bold"
tools:text="14 feb"/>
<TextView
android:id="#+id/idTvStartTime"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.2"
android:gravity="center"
android:includeFontPadding="false"
android:text="19:30" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#android:drawable/arrow_up_float" />
</LinearLayout>
</layout>
PS: I don't see <layout> in your row_item, are you sure you're doing binding correctly ?

Adding multiple OnClickListeners to a RecyclerView using Kotlin

I'm in the process of fiddling my way to a working app using Kotlin and I have hit a roadblock when trying to implement OnClickListeners for my three buttons. I have my RecyclerView populate properly, but despite following the recommendations on this SO post (except in Kotlin) and following the documentation, though I am still having trouble getting the implementation to work.
The code below is my adapter class for the implementation.
class BrowseHabitsAdapter(private val habits: ArrayList<Habit>) :
RecyclerView.Adapter<BrowseHabitsAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.habit_card, parent, false)
return ViewHolder(itemView, object: HabitClickListener {
override fun onDecrease(position: Int) {
val streak = itemView.dayCounter.text.toString().toInt()
itemView.dayCounter.text = streak.dec().toString()
}
override fun onIncrease(position: Int) {
val streak = itemView.dayCounter.text.toString().toInt()
itemView.dayCounter.text = streak.inc().toString()
}
override fun onEdit(position: Int) {
TODO("Change Activity to Edit")
}
})
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val currentItem = habits[position]
holder.habitTitle.text = currentItem.title
holder.streak.text = currentItem.streak.toString()
}
override fun getItemCount() = habits.size
class ViewHolder(itemView : View, listener : HabitClickListener) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
val habitTitle: TextView = itemView.habitTitle
val streak: TextView = itemView.dayCounter
val decreaseCounterButton : Button = itemView.decreaseCounterButton
val increaseCounterButton : Button = itemView.increaseCounterButton
val listener = listener
init {
decreaseCounterButton.setOnClickListener(this)
increaseCounterButton.setOnClickListener(this)
}
override fun onClick(v: View?) {
when (itemView.id) {
itemView.decreaseCounterButton.id -> listener.onDecrease(this.layoutPosition)
itemView.increaseCounterButton.id -> listener.onIncrease(this.layoutPosition)
}
}
}
interface HabitClickListener {
fun onDecrease(position : Int)
fun onIncrease(position : Int)
fun onEdit(position : Int)
}
}
and the following is my XML code defining one of my cards:
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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"
android:id="#+id/cardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
app:cardBackgroundColor="#eeeeee"
app:cardCornerRadius="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="#+id/cardHeader"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="#+id/habitTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:layout_marginRight="10dp"
android:text="#string/default_card_title"
android:textSize="18sp" />
<Space
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1" />
<ImageView
android:id="#+id/settingsIcon"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_gravity="bottom"
android:layout_marginRight="10dp"
app:srcCompat="#android:drawable/ic_menu_manage" />
</LinearLayout>
<LinearLayout
android:id="#+id/cardControls"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="#+id/decreaseCounterButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="-"
android:textAllCaps="false"
android:textSize="30sp" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<TextView
android:id="#+id/dayCounter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:fontFamily="sans-serif-medium"
android:text="0"
android:textAlignment="center"
android:textSize="30sp"
android:textStyle="bold" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<Button
android:id="#+id/increaseCounterButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="+"
android:textSize="30sp" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
Any additional explanation that can be provided as to what I did wrong and what is going on in detail would be really appreciated!
You are in kotlin so need to implement View.OnClickListener you can directly use setOnClickListener on any view.
Inside your ViewHolder Class:
itemView.increaseCounterButton.setOnClickListener{
listener.onIncrease(this.layoutPosition)
}
itemView.decreaseCounterButton.setOnClickListener{
listener.onDecrease(this.layoutPosition)
}
It should be view?.id instead of itemView.id
override fun onClick(v: View?) {
when (v?.id) {
itemView.decreaseCounterButton.id -> listener.onDecrease(this.layoutPosition)
itemView.increaseCounterButton.id -> listener.onIncrease(this.layoutPosition)
}
}
Additionally, your code with bugs. You handle HabitClickListener only update UI, when you scroll your data will be update base on habits. It means it will be revert when you scroll. Make sure streak of model Habit is var
return ViewHolder(itemView, object: HabitClickListener {
override fun onDecrease(position: Int) {
habits[position].streak = habits[position].streak.dec()
itemView.dayCounter.text = shabits[position].streak.toString()
}
override fun onIncrease(position: Int) {
habits[position].streak = habits[position].streak.inc()
itemView.dayCounter.text = shabits[position].streak.toString()
}
override fun onEdit(position: Int) {
TODO("Change Activity to Edit")
}
})

Categories

Resources