Populate RecyclerView from Firestore async call - android

I'm able to retrieve data from Firestore (it is definetly available inside the GlobalScope of populateValletList() and I was able to populate my RecyclerView from an asnyc database call or when I simply added a Vallet to my items list manually inside populateValletList(), however, when I want to populate that View from Firestore data it doesn't work. The
onBindViewHolder in RecyclerAdapter doesn't get called anymore
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when(holder){
is ValletViewHolder ->{
holder.bind(items[position])
}
}
}
that's how I populate my items private var items : MutableList<Vallet> = ArrayList() :
fun populateValletList() {
GlobalScope.launch {
items = getAllValletsFromDatabase.executeUseCase()
}
}
I init my RecyclerView inside onActivityCreated
private fun initRecyclerView(){
recycler_view_vallets.apply{
layoutManager = LinearLayoutManager(context)
addItemDecoration(ValletRecyclerAdapter.ValletItemDecorator(30))
valletAdapter = ValletRecyclerAdapter()
adapter = walletAdapter
}
valletAdapter.populateValletList()
}
This is my first time working with coroutines, what am I overlooking here?

Ok, I think there is some improvements that you could in your code, but the reason I believe is not working the way you want is because I you update your Items List, you have to call notifyDataSetChanged() on your adapter.
Ideally u should run ur coroutine using a scope that is not global, to avoid leaks, you can use a viewmodel for it, or you could use lifecycleScope.run { } in ur fragment, for that I believe you will need to add a depedency.
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
And regarding update your recycler, I recommend using ListAdapter, so it adds DIFF Utils and makes easier to update values.
To sum up.
Ur Recycler Adapter would be like this:
class HomePatchesAdapter : ListAdapter<Vallet, RecyclerView.ViewHolder>(REPO_COMPARATOR) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return HomePatchesViewHolder.create(parent, viewType)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val repoItem = getItem(position)
if (repoItem != null) {
(holder as HomePatchesViewHolder).bind(repoItem)
}
}
companion object {
private val REPO_COMPARATOR = object : DiffUtil.ItemCallback<Vallet>() {
override fun areItemsTheSame(oldItem: Vallet, newItem: Vallet): Boolean =
oldItem.name == newItem.name
override fun areContentsTheSame(oldItem: Vallet, newItem: Vallet): Boolean =
oldItem == newItem
}
}
}
and ur fragment would be like this:
private fun initRecyclerView(){
recycler_view_vallets.apply{
layoutManager = LinearLayoutManager(context)
addItemDecoration(ValletRecyclerAdapter.ValletItemDecorator(30))
}
val adapter = HomePatchesAdapter()
recycler_view_vallets?.adapter = adapter
lifecycleScope.run {
adapter.submitList(getAllValletsFromDatabase.executeUseCase())
}
}
Let me know if it makes sense.

Related

How to update the data passed to the adapter without recreating it android kotlin

Faced such a problem: I have a RecyclerView, the data for which I get from the ViewModel using StateFlow:
viewModel.items.collect {
setRecyclerView(items)
}
Then, let's say, somewhere inside the Fragment, I change the data for items and there are more of them. In order for my RecyclerView to see my changes, I have to call the setRecyclerView(items) function again, which, it seems to me, can lead to the most unexpected consequences (for example, items will be duplicated). The question is: is it possible to somehow change the data and update the RecyclerView (including the onBindViewHolder function in it) without yet another creation of an Adapter?
Let's start talking about the adapter implementation. Reading your question, I believe you used RecyclerView.Adapter to implement your adapter. But there is another option that is simpler and more performant than this. It's the ListAdapter:
The most interesting thing about ListAdapter is the DiffUtil, that have a performative way to check if any item on your list was updated, deleted, or included. Here's a sample of the implementation:
abstract class MyAdapter: ListAdapter<ItemModel, MyAdapter.MyViewHolder>(DIFF_CALLBACK) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val binding = ItemSimplePosterBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return MyViewHolder(binding)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bind(getItem(position))
}
class MyViewHolder(
private val binding: ItemSimplePosterBinding
): RecyclerView.ViewHolder(binding.root) {
fun bind(item: ItemModel) {
// Here you can get the item values to put these values on your view
}
}
companion object {
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<ItemModel>() {
override fun areItemsTheSame(oldItem: ItemModel, newItem: ItemModel): Boolean {
// need a unique identifier to have sure they are the same item. could be a comparison of ids. In this case, that is just a list of strings just compares like this below
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: ItemModel, newItem: ItemModel): Boolean {
// compare the objects
return oldItem == newItem
}
}
}
}
So, when your list is updated, you just have to call the submitList from the adapter, like this:
viewModel.items.collectLatest { items ->
// You will send the items to your adapter here
adapter.submitList(items)
}
Then, your RecyclerView just has to be configured on onViewCreated for example, and your list can be defined and updated in another place, observing the items change from ViewModel.

why can't I set a setOnLongClick in this recyclerview adapter?

The problem: I haven't found a solution that works and allows me to use onLongClickListener in my recyclerview adapter
I understand that my options seem to be either implementing an interface or using a lambda, however I've been trying everything I can find and none of them are working.
places I have tried solutions from:
Item Onclick RecyclerView Kotlin Android
RecyclerView onClick in kotlin
How to add a click listener to my recycler view (Android kotlin)
RecyclerView itemClickListener in Kotlin
Recyclerview Card Item Onclick Kotlin
and everything else I could find when googling "kotlin recyclerview onClickAdapter"
I get different errors with every solution, but the main takeaway is that none of them are working, which tells me that there is probably a problem with my adapter code to begin with.
Example of one error I get: If I try to use setOnClickListener in the bind function of TaskViewHolder, I get the error of
Wrong return type, expecting Boolean, received Unit
There is no passed in list because I use submitList from a viewmodel with a room database
The adapter code is based off of the Room with a View code
I have
Adapter Code:
class TaskRvAdapter : ListAdapter<Task, TaskRvAdapter.TaskViewHolder>(TaskComparator()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TaskViewHolder {
return TaskViewHolder.create(parent)
}
override fun onBindViewHolder(holder: TaskViewHolder, position: Int) {
val current = getItem(position)
holder.bind(current.task)
}
class TaskViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val taskItemView: TextView = itemView.findViewById(R.id.task_rv_item)
fun bind(text: String?) {
taskItemView.text = text
}
companion object {
fun create(parent: ViewGroup): TaskViewHolder {
val view: View = LayoutInflater.from(parent.context)
.inflate(R.layout.task_rv_item, parent, false)
return TaskViewHolder(view)
}
}
}
class TaskComparator : DiffUtil.ItemCallback<Task>() {
override fun areItemsTheSame(oldItem: Task, newItem: Task): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: Task, newItem: Task): Boolean {
return oldItem.task == newItem.task
}
}
}
try with this code, properly formatted
fun bind(text: String?) {
taskItemView.text = text
itemView.setOnClickListener {
Log.i("TaskRvAdapter", "item clicked: "+text)
}
itemView.setOnLongClickListener{
Log.i("TaskRvAdapter", "item long clicked: "+text)
return#setOnLongClickListener true
}
}

Items in recyclerview are not shown unless declaring adapter again in the observer methode

I'm having a problem with my adapter/recyclerview. I want the user to see an overview of appointments like this:
But i can only do this be setting the adapter for the recylerview in the observe method.
This the code:
val adapter = CalendarAdapter()
binding.appointmentList.adapter = adapter
calendarDataViewModel?.appointments?.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.data = it
binding.appointmentList.adapter = adapter
}
})
Like you see, if I set the adapter again inside the observe method, the items will be shown, but if I don't do that, nothing will be shown and it will just be an empty screen.
I also tried the following code, but this also doesnt work:
val adapter = CalendarAdapter()
binding.appointmentList.adapter = adapter
calendarDataViewModel?.appointments?.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.submitList(it)
}
})
I can't seem to figure out why this isnt working.
Appointments is of the type LiveData<List>
Does anyone know how I can fix this?
This is my CalendarAdapter:
class CalendarAdapter : ListAdapter<CalendarData, RecyclerView.ViewHolder>(AppointmentDiffCallback()) {
var data = listOf<CalendarData>()
override fun getItemCount(): Int {
return data.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return AppointmentViewHolder(CalendarItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val appointment = data[position]
(holder as AppointmentViewHolder).bind(appointment)
}
}
private class AppointmentDiffCallback : DiffUtil.ItemCallback<CalendarData>() {
override fun areItemsTheSame(oldItem: CalendarData, newItem: CalendarData): Boolean {
return oldItem.appointmentId == newItem.appointmentId
}
#SuppressLint("DiffUtilEquals")
override fun areContentsTheSame(oldItem: CalendarData, newItem: CalendarData): Boolean {
return oldItem == newItem
}
}
You do not need to override the getItemCount method. In your case, data.size returns 0 due to which the items are never shown.
Also, you do not need to keep track of your items, ListAdapter abstract class already does that. You can get your Item using getItem(Int) method. Your adapter should be something like so:
class CalendarAdapter : ListAdapter<CalendarData, RecyclerView.ViewHolder>(AppointmentDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return AppointmentViewHolder(CalendarItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val appointment = getItem(position)
(holder as AppointmentViewHolder).bind(appointment)
}
}
Then in your observer you can the submitList method like in your second code block in the question.

how to add, remove the ListAdapter Items in android?

I know The ListAdpater extends a recyclerview adapter. And for initializing items, I use submitList method. the parameter in submitList method is defined like this, typed list.
public void submitList(#Nullable List<T> list) {
mDiffer.submitList(list);
}
I can't remove or add items. So I tried using arraylist variable for adding and removing, and casted it to List. But I'm not sure this is the good way...
Because arraylist is not good to add or remove items. Is there other way?
Delete item from ListAdapter. (Kotlin)
fun removeItem(position: Int){
val currentList = adapter.currentList.toMutableList()
currentList.removeAt(position)
adapter.submitList(currentList)
}
Tested and working fine.
Just call submitList with a new list, the diffutil will take care of the old list and the new list.
If you add or remove something, add to your original list or remove an item from your list and then call submitList.
you can call use adapter.currentList to get your current list, or just use a viewModel to hold your list items.
MyAdapter
class FavoritesAdapter(
private val listeners: OnFavoriteDetailPage,
private val itemClick: (title: String, price: String) -> Unit) :ListAdapter<Favorites, FavoritesAdapter.RecyclerViewHolder>(ListComparator()) {
//bind the recycler list items
inner class RecyclerViewHolder(val binding: FavoriteListBinding) :
RecyclerView.ViewHolder(binding.root) {
#SuppressLint("SetTextI18n")
fun bind(list: Favorites) {
binding.deal.text = "Deal " + list.id.toString()
binding.price.text = "Ghc " + list.price
binding.pizzaSize.text = list.size.toString()
binding.posterBanner.load(list.imgUrl)
}
}
inflate the List
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerViewHolder {
return RecyclerViewHolder(
FavoriteListBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
}
//bind the model list the recycler list
#SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: RecyclerViewHolder, position: Int) {
val getItemPosition = getItem(position)
holder.bind(getItemPosition)
Glide.with(holder.itemView.context).load(getItemPosition.imgUrl)
.into(holder.binding.posterBanner)
holder.binding.addCart.setOnClickListener {
itemClick("Deal ${getItemPosition.id}", getItemPosition.price)
}
holder.itemView.setOnClickListener {
listeners.viewDetail(getItemPosition)
}
holder.binding.favoriteHeart.setOnClickListener {
listeners.onItemRemoveClick(getItemPosition.id)
}
}
class ListComparator : DiffUtil.ItemCallback<Favorites>() {
override fun areItemsTheSame(oldItem: Favorites, newItem: Favorites): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Favorites, newItem: Favorites): Boolean {
return oldItem.id == newItem.id
}
}
interface OnFavoriteDetailPage {
fun viewDetail(favorites: Favorites)
fun onItemRemoveClick(position: Int)
}
}
Inside my fragment
override fun onItemRemoveClick(position: Int) {
val currentList = recyclerAdapter.currentList.toMutableList()
currentList.removeAt(position)
recyclerAdapter.submitList(currentList)
}
Everythings works fine but mine delete from the bottom of the list instead of the current postion. What could be the problem?

AsyncListDiffer is not updating the recyclerview

I have a RecyclerView with an adapter that uses AssyncListDiffer. The problem I have is that the recyclerview is not updated when changes happen in the LiveData. The observer is notified but the list doesn't update.
This is my adapter:
class HourAdapter(private val interaction: HourInteraction? = null) :
RecyclerView.Adapter<HourAdapter.HourViewHolder>() {
private val differ = AsyncListDiffer(this, DIFF_CALLBACK)
fun submitList(list: List<Hour>?) {
differ.submitList(list)
}
private fun getHourAt(position: Int): Hour {
return differ.currentList[position]
}
override fun getItemCount(): Int {
return differ.currentList.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HourViewHolder {...}
override fun onBindViewHolder(holder: HourViewHolder, position: Int) {...}
val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Hour>() {
override fun areItemsTheSame(oldItem: Hour, newItem: Hour): Boolean {
return (oldItem.name == newItem.name) && (oldItem.isChecked == newItem.isChecked)
}
override fun areContentsTheSame(oldItem: Hour, newItem: Hour): Boolean {
return oldItem == newItem
}
}
class HourViewHolder
(
internal val binder: HourItemBinding
) : RecyclerView.ViewHolder(binder.root)
}
I use the "submitList()" to submit the new list. But it doesn't work.
I looked for solutions online and basically there were 2 options:
In the submitList function, call the "submitList" of AssyncListDiffer twice like this:
differ.submitList(null)
differ.submitList(list)
}```
The second option was to use ListAdapter and override the "submitList" function like this:
override fun submitList(list: List<Hour>?) {
super.submitList(list?.let { ArrayList(it) })
}
The first solution works, but the recyclerview blinks whenever I update it.
The second solution to override the method does not work for me.
I've been trying to fix this for days, but I can't make it work. Also, I don't want to use notifyItemChanged() or notifyDataSetChanged().
Is there another way?
I came around the same thing and observed the following.
Each time AsyncListDiffer received my list; it was the same object as previously - present in memory. Hence, the differ decided nothing changed and did not submit the updated list.
My list contained one object inside, and for each submission attempt I was changing one field. The object and the list of course remained the same.
So, I wondered why option number 2 did not work, and turned out that I needed to be a little more expressive:
submitList(it.map {
it.copy()
})
Otherwise, Kotlin would not make a deep copy of the object.
I was trying to delete a row and had the same problem, but solved it with the following:
The below code is added in the fragment:
private void deleteRow(int position) {
ArrayList<Card> cardsArrayList = adapter.getArrayList();
cardsArrayList.remove(position);
adapter.submitList(cardsArrayList);
}
And this code is in the adapter:
public ArrayList<Card> getArrayList(){
List<Card> cardList = mDiffer.getCurrentList();
return new ArrayList<>(cardList) ;
}

Categories

Resources