i am making an app in which i have to insert data into reyclerview , the recyclerview is working fine but the problem is that when i scroll it up adapter reupdate the data , so to solve this issue the code looks fine but still getting this issue ....................................................
BaseClass
abstract class MultiViewModelBaseAdapter<M : Model, VDB : ViewDataBinding>(private var diffCallback: DiffUtil.ItemCallback<M>) : ListAdapter<M ,BaseViewHolder<VDB>>(diffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<VDB> {
val inflator = LayoutInflater.from(parent.context)
val binding = createBinding(viewType, inflator, parent)
return BaseViewHolder(binding)
}
abstract fun createBinding(viewType: Int, inflater: LayoutInflater, parent: ViewGroup) : VDB
override fun onBindViewHolder(holder: BaseViewHolder<VDB>, position: Int) {
bind(holder.mBinding, getItem(position), position)
holder.mBinding.executePendingBindings()
}
abstract fun bind(binding: VDB, item: M, position: Int)
abstract fun onDataChanged(values: Boolean)}
}
Adapter
class LanguageAdapter(
private val context: Context,
private val mViewModel: LanguageListViewModel,
private val onClickListener: OnItemClickListener<String>
) : MultiViewModelBaseAdapter<LanguageSupportModel, ViewDataBinding>(diffCallback) {
companion object {
private val ADS = 1
private val LANGUAGES = 2
val diffCallback = object : DiffUtil.ItemCallback<LanguageSupportModel>() {
override fun areItemsTheSame(
oldItem: LanguageSupportModel,
newItem: LanguageSupportModel
): Boolean = oldItem.dataId == newItem.dataId
/**
* Note that in kotlin, == checking on data classes compares all contents, but in Java,
* typically you'll implement Object#equals, and use it to compare object contents.
*/
override fun areContentsTheSame(
oldItem: LanguageSupportModel,
newItem: LanguageSupportModel
): Boolean = oldItem == newItem
}
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getItemViewType(position: Int): Int {
return position
}
override fun createBinding(viewType: Int, inflater: LayoutInflater, parent: ViewGroup): ViewDataBinding {
return DataBindingUtil.inflate(inflater, R.layout.language_view, parent, false)
}
override fun bind(binding: ViewDataBinding, item: LanguageSupportModel, position: Int) {
binding as LanguageViewDataBinding
binding.apply {
language = item
//click
}
}
override fun onDataChanged(values: Boolean) {}
}
Fragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val languageAdapter = LanguageAdapter(requireContext(), mViewModel, this ,lifecycleScope)
languageAdapter.submitList(LanguageArray.arrayValues())
reyclerview.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
adapter = languageAdapter
}
If You will Use Paging concept in Scrolling then it will Solve.
In the JAVA or KOTLIN We can implement.. like..
import androidx.viewpager.widget.PagerAdapter;
Then extends it into Adapter Class.
public void addList(List<ClsList> list) {
this.mResources = list;
notifyDataSetChanged(); // also this main line
}
Problem Solve.
☻♥ Have Fun..
Related
when i try to delete first row i get first row back and with duplicate second row
enter image description here
after delete any row from the list gives me duplicate data like this
enter image description here
This is my Adapter class. On my deleteItem function position is passed from fragment class and
it is supposed to delete an item from the given position and update the list but its rendering the duplicate data.
class MyListAdapter :ListAdapter<Article,MyListAdapter.MyViewHolder>(MyDiffUtil()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.recycler_item,parent,false)
return MyViewHolder(view)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val item = getItem(position)
holder.onBind(item)
// to click each item of list
holder.itemView.setOnClickListener {
onItemClickListener?.let {it(item) }
}
}
class MyViewHolder(itemView :View) : RecyclerView.ViewHolder(itemView)
{
fun onBind(article: Article) = with(itemView)
{
Glide.with(this)
.load(article.urlToImage)
.error(R.drawable.notfound)
.into(imageView)
webSource.text = article.source?.name
newsDate.text = article.publishedAt
newsTitle.text = article.title
newsDescription.text = article.description
}
}
// lamda function for handling web view
private var onItemClickListener : ((Article) -> Unit)? = null
fun setOnItemClickListener(listener : (Article) ->Unit)
{
onItemClickListener = listener
}
// implementing diffutil class
class MyDiffUtil : DiffUtil.ItemCallback<Article>()
{
override fun areItemsTheSame(oldItem: Article, newItem: Article): Boolean {
return oldItem.url == newItem.url
}
override fun areContentsTheSame(oldItem: Article, newItem: Article): Boolean {
return oldItem == newItem
}
}
fun deleteItem(pos : Int)
{
var myl = ArrayList<Article>()
val currentList= currentList.toMutableList()
currentList.removeAt(pos)
myl.addAll(currentList)
submitList(myl)
}
}
`
I tried to delete the saved news from the ListAdapter and update the recycler view with animation of DiffUtill class but its not updating the recycler view and
giving duplicate data . How can i delete data with diffutil animation as its normal way. Thanks in advance.
this is my fragment
package com.example.news.Fragments
class SavedNews : Fragment() {
lateinit var mymainViewModel: MainViewModel
lateinit var databaseObject: MyDataBase
lateinit var myAdapter: MyListAdapter
lateinit var convertedArticleList : ArrayList<Article>
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
myAdapter = MyListAdapter()
convertedArticleList = ArrayList()
return inflater.inflate(R.layout.fragment_saved_news, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
savedNewsRecyclerView.layoutManager = LinearLayoutManager(activity,LinearLayoutManager.VERTICAL,
false)
savedNewsRecyclerView.adapter = myAdapter
ItemTouchHelper(object :ItemTouchHelper.SimpleCallback(0,ItemTouchHelper.RIGHT){
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
// this method is called
// when the item is moved.
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
// this method is called when we swipe our item to right direction.
// on below line we are getting the item at a particular position.
val deletedCourse: Article =
convertedArticleList.get(viewHolder.adapterPosition)
myAdapter.deleteItem(viewHolder.adapterPosition)
mymainViewModel.deleteSavedNews(deletedCourse.id!!)
// myAdapter.notifyItemRemoved(viewHolder.adapterPosition)
// myAdapter.submitList(myCurrentList)
// Snackbar.make(savedNewsRecyclerView,"Deleted" + deletedCourse.title,
// Snackbar.LENGTH_LONG).setAction(
// "Undo",
// View.OnClickListener {
//
// convertedArticleList.add(position,deletedCourse)
// myAdapter.notifyItemInserted(position)
// }
// ).show()
}
}).attachToRecyclerView(savedNewsRecyclerView)
}
override fun onResume() {
super.onResume()
Log.d("LIFE","ON Resume")
val myInterfaceObject = ApiInterface.MyObject.getInstance()
databaseObject = MyDataBase.MyObjectDB.getDBInstance(activity as MainActivity)
val myRepository = Repository(myInterfaceObject, databaseObject)
mymainViewModel = ViewModelProvider(
this,
MainViewModelFactory(myRepository)
).get(MainViewModel::class.java)
//listAdapter things
// show saved news in savednews fragment
lifecycleScope.launch(Dispatchers.Main) {
//abstract data from saved room database and converting likedartcile datacass object to
// Artticle data class article
mymainViewModel.abstractSavedNews().observe(viewLifecycleOwner, Observer {
it.forEach { eachLikedArticle ->
val obj = toArticle(eachLikedArticle)
convertedArticleList.add(obj)
}
myAdapter.submitList(convertedArticleList)
})
//clicking the item of save news
myAdapter.setOnItemClickListener {
val bundle = Bundle().apply {
putSerializable("article", it)
}
convertedArticleList.clear()
findNavController().navigate(R.id.action_savedNews_to_article, bundle)
}
}
}
private fun toArticle(rawObject: LikedArticle) = Article(
rawObject.author, rawObject.content,
rawObject.description, rawObject.publishedAt, rawObject.source, rawObject.title,
rawObject.url, rawObject.urlToImage, rawObject.id
)
}
There is not enough information to actually replicate how you're trying to use this in your Fragment/Activity but without changing your code too much, this works (note I used view binding):
class MyListAdapter :
ListAdapter<Article, MyViewHolder>(MyDiffUtil()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = MyViewHolder(
RecyclerItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val item = getItem(position)
holder.onBind(item)
}
fun deleteItem(pos: Int) {
var myl = ArrayList<Article>()
val currentList = currentList.toMutableList()
currentList.removeAt(pos)
myl.addAll(currentList)
submitList(myl)
}
}
class MyViewHolder(private val binding: RecyclerItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun onBind(article: Article) = with(binding) {
textView.text = article.url
}
}
class MyDiffUtil : DiffUtil.ItemCallback<Article>() {
override fun areItemsTheSame(oldItem: Article, newItem: Article): Boolean {
return oldItem.url == newItem.url
}
override fun areContentsTheSame(oldItem: Article, newItem: Article): Boolean {
return oldItem == newItem
}
}
Overall this should work.
You can also refactor deleteItem to something more meaningful:
fun deleteItem(pos: Int) {
val oldList = currentList.toMutableList()
oldList.removeAt(pos)
val updatedList = oldList
submitList(updatedList)
}
I was following along the Android Room With a View tutorial.
The tutorial makes use of DiffUtil to compute changes in the list and update the RecyclerView accordingly.
However, when removing or adding items to the RecyclerView, DiffUtil always causes the entire RecyclerView to reload, instead of calling the correct notifyItemRemoved or notifyItemInserted.
My Adapter:
class MarksAdapter(private val context: Context) :
ListAdapter<Mark, MarksAdapter.MarkViewHolder>(MarksComparator()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MarkViewHolder {
return MarkViewHolder.create(parent)
}
override fun onBindViewHolder(holder: MarkViewHolder, position: Int) {
val mark = getItem(position)
holder.bind(context, mark)
}
class MarkViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private var mark: Mark? = null
fun bind(context: Context, mark: Mark) {
this.mark = mark
// Removed for brevity...
}
companion object {
fun create(parent: ViewGroup): MarkViewHolder {
val view: View =
LayoutInflater.from(parent.context).inflate(R.layout.card_view, parent, false)
return MarkViewHolder(view)
}
}
}
class MarksComparator : DiffUtil.ItemCallback<Mark>() {
override fun areItemsTheSame(oldItem: Mark, newItem: Mark): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: Mark, newItem: Mark): Boolean {
return oldItem == newItem
}
}
}
From the activity/fragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.recyclerViewMarks.apply {
marksAdapter = MarksAdapter(context)
adapter = marksAdapter
layoutManager = LinearLayoutManager(this#MarksOverviewFragment.requireContext())
}
marksViewModel.allMarks.observe(viewLifecycleOwner) { marks ->
marks?.let { marksAdapter.submitList(it) }
}
}
After thinking about it some more, the only place where I can actually influence what DiffUtil does, is within the DiffUtil.Callback.
So after changing:
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return newList[newItemPosition] === oldList[oldItemPosition]
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return newList[newItemPosition] == oldList[oldItemPosition]
}
to:
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return newList[newItemPosition].Uid == oldList[oldItemPosition].Uid
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return newList[newItemPosition] == oldList[oldItemPosition]
}
(notice the comparison of Uid instead of identity)
...the DiffUtil works as expected.
What I'm not sure however, is why it worked in the tutorial, but not for me.
I created a RecyclerView that refreshes its list based on a database call. Each row has an options menu that is revealed when the user swipes. My original issue was that after an orientation change, the swipe gestures no longer revealed the menu. I hit all my expected breakpoints with onCreateViewHolder() and the onSwipe(). However, the row remained as the HIDE_MENU view type after swiping.
So I tried to introduce LiveData to persist the state of the list after orientation changes. The RecyclerView was still created and populated with items but now the swipe gesture crashes the application with an error:
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
Do I need to use LiveData to fix the original issue of preserving my swipe functionality after orientation changes? If not, please can someone explain why the item view types are no longer updated after orientation changes.
If I do need to use a ViewModel, what am I doing that is causing the list adapter not to receive the updated list?
HistoryFragment
class HistoryFragment : Fragment() {
private val historyViewModel by activityViewModels<HistoryViewModel>()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val root = inflater.inflate(R.layout.fragment_history, container, false)
historyViewModel.getHistoryList().observe(viewLifecycleOwner, {
refreshRecyclerView(it)
})
return root
}
private fun updateHistoryList() {
val dbHandler = MySQLLiteDBHandler(requireContext(), null)
val historyList = dbHandler.getHistoryList() as MutableList<HistoryObject>
historyViewModel.setHistoryList(historyList)
}
private fun refreshRecyclerView(historyList: MutableList<HistoryObject>) {
val historyListAdapter = HistoryListAdapter(historyList)
val callback = HistorySwipeHelper(historyListAdapter)
val helper = ItemTouchHelper(callback)
history_list.adapter = historyListAdapter
helper.attachToRecyclerView(history_list)
}
private fun setupSort() {
val sortSpinner: Spinner = history_list_controls_sort
sortSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {}
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
updateHistoryList()
}
}
}
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
setupSort()
}
}
HistoryListAdapter
const val SHOW_MENU = 1
const val HIDE_MENU = 2
class HistoryListAdapter(private var historyData: MutableList<HistoryObject>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == SHOW_MENU) {
val inflatedView = LayoutInflater.from(parent.context).inflate(R.layout.history_list_view_row_items_menu, parent, false)
MenuViewHolder(inflatedView)
} else {
val inflatedView = LayoutInflater.from(parent.context).inflate(R.layout.history_list_view_row_items_description, parent, false)
HistoryItemViewHolder(inflatedView)
}
}
override fun getItemViewType(position: Int): Int {
return if (historyData[position].showMenu) {
SHOW_MENU
} else {
HIDE_MENU
}
}
override fun getItemCount(): Int {
return historyData.count()
}
fun showMenu(position: Int) {
historyData.forEachIndexed { idx, it ->
if (it.showMenu) {
it.showMenu = false
notifyItemChanged(idx)
}
}
historyData[position].showMenu = true
notifyItemChanged(position)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item: HistoryObject = historyData[position]
if (holder is HistoryItemViewHolder) {
holder.bindItem(item)
...
}
if (holder is MenuViewHolder) {
holder.bindItem(item)
...
}
}
class HistoryItemViewHolder(v: View, private val clickHandler: (item: HistoryObject) -> Unit) : RecyclerView.ViewHolder(v) {
private var view: View = v
private var item: HistoryObject? = null
fun bindItem(item: HistoryObject) {
this.item = item
...
}
}
class MenuViewHolder(v: View, private val deleteHandler: (item: HistoryObject) -> Unit) : RecyclerView.ViewHolder(v) {
private var view: View = v
private var item: HistoryObject? = null
fun bindItem(item: HistoryObject) {
this.item = item
...
}
}
}
HistorySwipeHelper
class HistorySwipeHelper(private val adapter: HistoryListAdapter) : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { return false }
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
adapter.showMenu(viewHolder.adapterPosition)
}
override fun getSwipeThreshold(viewHolder: RecyclerView.ViewHolder): Float {
return 0.1f
}
}
HistoryViewModel
class HistoryViewModel(private var historyListHandle: SavedStateHandle) : ViewModel() {
fun getHistoryList(): LiveData<MutableList<HistoryObject>> {
return historyListHandle.getLiveData(HISTORY_LIST_KEY)
}
fun setHistoryList(newHistoryList: MutableList<HistoryObject>) {
historyListHandle.set(HISTORY_LIST_KEY, newHistoryList)
}
companion object {
const val HISTORY_LIST_KEY = "MY_HISTORY_LIST"
}
}
Activity
class MainActivity : AppCompatActivity() {
private val historyViewModel: HistoryViewModel by lazy {
ViewModelProvider(this).get(HistoryViewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
historyViewModel.setHistoryList(mutableListOf())
}
}
Thanks in advance. If this question is too broad I can try again and decompose it.
You shouldn't create new adapter every time you get an update of your history list. Keep using the same adapter, just update the items and call notifyDataSetChanged() to update the state (of course you can use different methods to notify about the insertion/deletion/etc, but make it work with notifyDataSetChanged() first).
I'm pretty sure this will fix the issue.
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!!)
}
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
}
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
}
}
Can I use ViewBindings to replace findViewById in this typical RecyclerView.Adapter initialization code? I can't set a binding val in the object as the ViewHolders are different per cell.
class CardListAdapter(private val cards: LiveData<List<Card>>) : RecyclerView.Adapter<CardListAdapter.CardViewHolder>() {
class CardViewHolder(val cardView: View) : RecyclerView.ViewHolder(cardView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder {
val binding = CardBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return CardViewHolder(binding.root)
}
override fun onBindViewHolder(holder: CardViewHolder, position: Int) {
val title = holder.cardView.findViewById<TextView>(R.id.title)
val description = holder.cardView.findViewById<TextView>(R.id.description)
val value = holder.cardView.findViewById<TextView>(R.id.value)
// ...
}
What you need to do is pass the generated binding class object to the holder class constructor. In below example, I have row_payment XML file for RecyclerView item and the generated class is RowPaymentBinding so like this
class PaymentAdapter(private val paymentList: List<PaymentBean>) : RecyclerView.Adapter<PaymentAdapter.PaymentHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PaymentHolder {
val itemBinding = RowPaymentBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return PaymentHolder(itemBinding)
}
override fun onBindViewHolder(holder: PaymentHolder, position: Int) {
val paymentBean: PaymentBean = paymentList[position]
holder.bind(paymentBean)
}
override fun getItemCount(): Int = paymentList.size
class PaymentHolder(private val itemBinding: RowPaymentBinding) : RecyclerView.ViewHolder(itemBinding.root) {
fun bind(paymentBean: PaymentBean) {
itemBinding.tvPaymentInvoiceNumber.text = paymentBean.invoiceNumber
itemBinding.tvPaymentAmount.text = paymentBean.totalAmount
}
}
}
Also, make sure you pass the root view to the parent class of Viewholder like this RecyclerView.ViewHolder(itemBinding.root) by accessing the passed binding class object.
Attach the binding to the ViewHolder instead of the View
class CardViewHolder(val binding: CardBinding) : RecyclerView.ViewHolder(binding.root)
You pass the binding, the binding passes binding.root to RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder {
val binding = CardBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return CardViewHolder(binding)
}
Then access anywhere with:
holder.binding.title
I wrote a simple and reusable one:
class ViewBindingVH constructor(val binding: ViewBinding) :
RecyclerView.ViewHolder(binding.root) {
companion object {
inline fun create(
parent: ViewGroup,
crossinline block: (inflater: LayoutInflater, container: ViewGroup, attach: Boolean) -> ViewBinding
) = ViewBindingVH(block(LayoutInflater.from(parent.context), parent, false))
}
}
class CardAdapter : RecyclerView.Adapter<ViewBindingVH>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewBindingVH {
return ViewBindingVH.create(parent, CardBinding::inflate)
}
override fun onBindViewHolder(holder: ViewBindingVH, position: Int) {
(holder.binding as CardBinding).apply {
//bind model to view
title.text = "some text"
descripiton.text = "some text"
}
}
}
You may use view-binding like this :
package com.example.kotlinprogramming.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.kotlinprogramming.data.HobbiesData
import com.example.kotlinprogramming.databinding.ItemHobbieBinding
class HobbiesAdapter(var context: Context, var hobbiesList: List<HobbiesData>) :
RecyclerView.Adapter<HobbiesAdapter.HobbiesViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HobbiesViewHolder {
val view = ItemHobbieBinding.inflate(LayoutInflater.from(context) , parent,false)
return HobbiesViewHolder(view)
}
override fun onBindViewHolder(holder: HobbiesViewHolder, position: Int) {
val hobbie = hobbiesList.get(position)
holder.viewBinding.tvHobbie.text = hobbie.title
}
inner class HobbiesViewHolder(var viewBinding: ItemHobbieBinding) : RecyclerView.ViewHolder(viewBinding.root) {
}
override fun getItemCount(): Int {
return hobbiesList.size
}
}
Here is item_hobbies.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_margin="12dp"
android:layout_height="wrap_content"
>
<TextView
android:id="#+id/tvHobbie"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:gravity="center"
android:textSize="30sp"
tools:text="Hobbie1"
/>
</androidx.cardview.widget.CardView>
If you're ok with reflection, I have a much easier way to do this.
Just call ViewGroup.toBinding() then you can get the binding object you want.
But since we're talk about reflection, remember you have to modify your proguard-rule to make it work even for proguard.
// inside adapter
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return MainViewHolder(parent.toBinding())
}
// ViewHolder
class MainViewHolder(private val binding: AdapterMainBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(data: String) {
binding.name.text = data
}
}
// The magic reflection can reused everywhere.
inline fun <reified V : ViewBinding> ViewGroup.toBinding(): V {
return V::class.java.getMethod(
"inflate",
LayoutInflater::class.java,
ViewGroup::class.java,
Boolean::class.java
).invoke(null, LayoutInflater.from(context), this, false) as V
}
I put all this into an open source project you can take a look as well.
Not only for Adapter usage but also include Activity and Fragment. And do let me know if you have any comment. Thanks.
https://github.com/Jintin/BindingExtension
just pass your model calss into xml and set these data into xml this code look fine and add a method where you add these data into binding like you don,t need to fine the id for this
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.setData(listData[position])
}
fun setData(model: ListData) {
with(binding) {
data = model
executePendingBindings()
}
}
You may use data binding like this.
class CardListAdapter(
private val mActivity: FragmentActivity?
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var mCustomLayoutBinding: CustomLayoutBinding? = null
inner class MyViewHolder(val mBinding: CustomLayoutBinding) :
RecyclerView.ViewHolder(mBinding.getRoot())
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
if (layoutInflater == null)
layoutInflater = LayoutInflater.from(parent.context)
var viewHolder: RecyclerView.ViewHolder? = null
val inflater = LayoutInflater.from(parent.context)
viewHolder = getViewHolder(parent, inflater)
return viewHolder!!
}
private fun getViewHolder(
parent: ViewGroup,
inflater: LayoutInflater
): RecyclerView.ViewHolder {
mCustomLayoutBinding =
DataBindingUtil.inflate(inflater, R.layout.custom_layout, parent, false)
return MyViewHolder(this!!.mAssistanceLogCustomLayoutBinding!!)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val taskModal = mArrayList.get(position)
holder.mBinding.txtTitle.setText(taskModal.title)
}
override fun getItemCount(): Int {
return assistanceArrayList.size
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getItemViewType(position: Int): Int {
return position
}
}
I took what Alan W. did and added Generics to it.
class ViewBindingVH <VB: ViewBinding> constructor(val binding: VB) :
RecyclerView.ViewHolder(binding.root) {
companion object {
inline fun <VB: ViewBinding> create(
parent: ViewGroup,
crossinline block: (inflater: LayoutInflater, container: ViewGroup, attach: Boolean) -> VB
) = ViewBindingVH<VB>(block(LayoutInflater.from(parent.context), parent, false))
}}
The implementation is very easy and you avoid the casting on the adapter class:
class PlayerViewHolder : ListAdapter<Rate, ViewBindingVH<ChartItemBinding>>(RateDiff) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewBindingVH<ChartItemBinding> =
ViewBindingVH.create(parent, ChartItemBinding::inflate)
override fun onBindViewHolder(holder: ViewBindingVH<ChartItemBinding>, position: Int) {
val item = currentList[position]
holder.binding.apply {
}
}}
object RateDiff: DiffUtil.ItemCallback<Rate>(){
override fun areContentsTheSame(oldItem: Rate, newItem: Rate): Boolean {
return oldItem == newItem
}
override fun areItemsTheSame(oldItem: Rate, newItem: Rate): Boolean {
return oldItem == newItem
}}
abstract class BaseRecyclerViewAdapter<T : Any, VB : ViewBinding>(
private var dataList: ArrayList<T>)
: RecyclerView.Adapter<BaseRecyclerViewAdapter.MyViewViewHolder<VB>>()
{
protected var bindingInterface: GenericSimpleRecyclerBindingInterface<T, VB>? = null
class MyViewViewHolder<VB : ViewBinding>(val viewBinding: VB) :
RecyclerView.ViewHolder(viewBinding.root) {
fun <T : Any> bind(
item: T,
position: Int,
bindingInterface: GenericSimpleRecyclerBindingInterface<T, VB>
) = bindingInterface.bindData(item, position, viewBinding)
}
#SuppressLint("NotifyDataSetChanged")
fun updateList(list: ArrayList<T>) {
dataList.clear()
dataList.addAll(list)
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):MyViewViewHolder<VB> {
val view = inflateView(LayoutInflater.from(parent.context))
return MyViewViewHolder(view)
}
override fun onBindViewHolder(holder: MyViewViewHolder<VB>, position: Int) {
val item = dataList[position]
holder.bind(item, position, bindingInterface!!)
}
override fun getItemCount(): Int = dataList.size
abstract fun inflateView(inflater: LayoutInflater): VB
}
interface GenericSimpleRecyclerBindingInterface<T : Any, VB : ViewBinding> {
fun bindData(item: T, position: Int, viewBinding: VB)
}