Android|Kotlin recyclerView update one item - android

I have this issue:
I'm using a recycler view in my app but the thing is that I need to update one single item of my recycler view.
When I click on the recycler view item, it sends me to another screen where I update the value and returned (this is working, I get the values on back)
I tried to add a new function inside my adapter as follows:
public fun updateItem(updatedOI: ArrayList<OrderItem>){
orderItem = updatedOI
notifyDataSetChanged()
}
and then call the function from my fragment/activity but it is not working.
I basically need to grab the index somehow and update that single index what it is doing now is removing all of the items and just add the updated one.
How do I fix this issue?

The problem is you can't update the data by making it reference another.
fun update(items: List<OrderItem>) {
orderItem.clear()
orderItem.addAll(items)
notifyDataSetChanged()
}
I'm using a list here because it is easier than working with arrays, your inner data should be a mutable list.
If you only want to update 1 item. You can get the index and then pass it back
//in the fragment/activity
intent.putExtra(INDEX, youClickedIndex)
and then set the result back like you are allegedly doing
fun update(item: OrdertItem, index: Int) {
orderItem.add(index, item)
notifyItemChanged(index)
}

Related

Get TextView string from recyclerview

Recyclerview contain cardlayout with three textviews(name, email, phone) fetched from database. I want to hide the cardlayout from the recyclerview if it contains name equal to certain string. What i tried is after fetching data from database, i used if statement to compare name with string and if it's equal them make the cardlayout invisible. but many cardlayout are getting invisible if condition satisfy
if (dealer.getName().equals("abcde"))
cardlayout.setVisibility(View.GONE);
It looks to me like your RecyclerView items are having multiple items with visibility of Gone.
And I'm sure you're doing that logic in onBindViewHolder() in RecyclerView.Adapter
Why are you in such a situation?
onBindViewHolder() is called with that item when the item is init , and when you scroll to another position you don't see that item anymore and scroll back to that item position.
In the RecyclerView.Adapter documentation, Google says about onBindViewHolder():
Called by RecyclerView to display the data at the specified position. This method should update the contents of the RecyclerView.ViewHolder.itemView to reflect the item at the given position.
And
you should only use the position parameter while acquiring the related data item inside this method and should not keep a copy of it.
For this reason, When you re-scroll the item before it or after it, and you don't set an else case for your item, then the value of the item before it or after it in onBindViewHolder will get your content back on the item where you set the cardlayout hidden logic.
How to solve the problem?
For hidden logic that shows or modifies view items in onBindViewHolder(), make sure that if you have case if you have case else.
YourRecyclerViewAdapter.kt
override fun onBindViewHolder(holder: YourViewHolder, position: Int) {
...
if (dealer.name == "abcde") {
cardLayout.visibility = View.GONE
} else { // You should have else case to return the correct content for other positions
cardLayout.visibility = View.VISIBLE
}
...
}

How to provide conditional visibility of a child-item in a RecyclerView in the Adapter class?

I am working on a project which implements 2 views for each screen, a normal user view, and an admin view. The admin view is presented with a little more privileges than a normal user like deleting certain posts or the users themselves from the database.
Therefore, I set the visibility of those functional buttons to be GONE if the admin privilege is true (which I pass as a parameter value when initializing the adapter). But what I am struggling with, is where do I set the visibility, in the onCreateViewHolder method or onBindViewHolder method? I have right now set it in the onCreateViewHolder method because I had read on some Stackoverflow answer only that we should avoid heavy operations in onBindViewHolder method. But I would like to know a definitive answer.
Here are the code samples for reference:
The adapter class declaration:
class NoticesAdapter(options: FirestoreRecyclerOptions<NoticeModel>,
private val isAdmin: Boolean,
private val listener: INoticeListAdapter):
FirestoreRecyclerAdapter<NoticeModel, NoticesAdapter.NoticeViewHolder>(options)
onCreateViewHolder meothod:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoticeViewHolder {
val noticeListView = LayoutInflater.from(parent.context).inflate(R.layout.item_notice, parent, false)
val noticeListViewHolder = NoticeViewHolder(noticeListView)
if (!isAdmin)
{
noticeListViewHolder.deleteNoticeBtn.visibility = GONE
}
// On clicking the delete button on a notice by the admin
noticeListViewHolder.deleteNoticeBtn.setOnClickListener {
val noticeSnapshot = snapshots.getSnapshot(noticeListViewHolder.adapterPosition)
listener.deleteNoticeBtnListener(noticeSnapshot)
}
return noticeListViewHolder
}
The onBindViewHolder method:
override fun onBindViewHolder(holder: NoticeViewHolder, position: Int, model: NoticeModel) {
holder.noticeText.text = model.noticeText
holder.noticeAuthor.text = MyUtils.getUserName()
holder.noticePostDate.text = model.datePosted
holder.noticePostTime.text = model.timePosted
}
A RecyclerView.Adapter what it does is to: recycle items (as the name implies). The list doesn't have one view per item on the data source at the same time. The adapter makes sure to have enough views in memory in order to always render the list smoothly. When a row is leaving the field of view by scrolling, then that view is recycled to be re-used in the next entering view to the screen size.
This means that onCreateViewHolder is called only when a view is needed to be created. Generally at the start of the adapter, also when the user is scrolling fast or erratically and when the data set changes and is needed.
The other method onBindViewHolder is called every time the data on the row needs to be updated in order for the view to get updated. This is called every time a row is entering the view field of the screen.
So the textbook answer is: do it on onBindViewHodlder, because if the attribute isAdmin changes then that row will need to be updated. By doing it on onCreateViewHolder that would only happen one time when the row is created.
But, your isAdmin is a val on the constructor that can not be reassigned, so this means that when the rows are created the button will be hidden or visible forever. And this doesn't matter because your structure is to determine if is admin from another source that is separated from which the row data structure is derived from.
If in some case you want to:
make it more flexible and easier to maintain in the future
or maybe you know there is going to be a case where there is gonna be a list with admins and not admins rows
Then the solution is to move the isAdming attribute to your NoticeModel, that would imply changing your data structure.
If you want to verify anything sai above, get a data source with plenty of items and then add 2 logs, one on onCreateViewHolder and one in onBindViewHolder. You will see how on create is called only sometimes but on bind is called always.

LiveData is duplicating MutableList

I want to update my Recyclerview items whenever there is a data change in Firestore.
So my activity have the following code:
taskViewModel.fetchedTaskLiveData.observe(
this, Observer {
if (it != null) {
todoListAdapter.setListData(it)
showRecyclerView()
}
Inside adapter setListData method:
fun setListData(data: MutableList<Todo>) {
//this.todoList.clear()
this.todoList.addAll(data)
notifyDataSetChanged()
}
In this situation, whenever a changed list is fetched, Recyclerview is duplicating and adding the changed
list below the old list.
And if "this.todoList.clear()" is uncommented, list is getting cleared and no data is showing on data change.
I tried all possible solutions, but I think somewhere am missing a part. stuck since a long time.
Thanks a lot for all kind of suggestion.
So I was stuck with this and found out after 12 hours a tiny change.
I corrected it by changing for with forEach and that changed the game.
Also I am clearing list items before adding the items.
todoList.clear()

Update all items in a RecyclerView asyncronsously on Android

I have a recycler view I want to update one attribute of each item after the initial creation to provide the user with a nicer experience. I would like to do this asynchronously as it takes time to get the data.
How do you iterate over the items in a recycler view and subsequently update. I moved from listview to recycler because it has a the method NotifyItemChanged.
So Ideally I would like to do
void OnRefresh(IList<data> data)
{
Data = data;
NotifyDataSetChanged();
Task.Run(() => UpdateAllAttribute1Fields());
}
void UpdateAllAttribute1Fields()
{
foreach(var myItem in myRecyclerView.Items)
{
UpdateAttribute1(myItem));
}
}
But I do not understand how to access myItems. On windows (sorry), in a listview this would be listview.items I think.
I could save the views OnBindViewHolder but that will be a bit more work.
Thanks all.
You dont need to iterate over all items in recyclerview to update, Just set the RecyclerView to point to your custom data set. And asynchronously update the data set whenever you want. You can then notify the recyclerview that the data in the dataset has changed with the following methods of its adapter (which will automatically update the recyclerview content)
adapterObj.notifyItemChanged(pos) //for one object
adapterObj.notifyDataSetChanged() //for the entire dataset
adapterObj.notifyItemRangeChanged(start, end) //for a range

Load image via URL into RecyclerAdapter

I have an API which returns JSON and I would like to load an Image from a URL which is provided by this API. The Image should be passed into Adapter for a Recycling View.
Right now all Items which contain an Imgae_URL are getting skipped by my Adapter and I dont really understand why.
if (json_img_url.isNotEmpty()) {
Executors.newSingleThreadExecutor().execute({
val conn = URL(json_img_url).openConnection()
conn.connect()
val iStream:InputStream = conn.getInputStream()
val img_bitmap:Bitmap? = BitmapFactory.decodeStream(iStream)
newItems.add(Item(....img_bitmap))
})
....
itemArrayAdapter.addItems(newItems)
URL :"https://s3.us-east-2.amazonaws.com/c...."
The URls used are valid and Images on the S3 Bucket are all public.
The If statment returns true (I checked with Log.d) but the Item does not appear on the Phone, I dont recive an error and the app does not crash its just like the Item was never there...
I know there are librarys like Picasso or Glide but even with them I could not make it work and to be honest I would like to accomplish this task without a having to install an extra package, it just feels wrong.
Unlike ListView, there is no way to add or remove items directly through the RecyclerView adapter. You need to make changes to the data source directly and notify the adapter of any changes.There are many method available to use when notifying the adapter of different changes:
notifyItemChanged(int pos) : Notify that item at position has changed.
notifyItemInserted(int pos): Notify that item reflected at position has been newly inserted.
notifyItemRemoved(int pos): Notify that items previously located at position has been removed from the data set.
notifyDataSetChanged(): Notify that the dataset has changed. Use only as last resort.
Every time we want to add or remove items from the RecyclerView, we will need to explicitly inform to the adapter of the event. Unlike the ListView adapter, a RecyclerView adapter should not rely on notifyDataSetChanged() since the more granular actions should be used. See the API documentation for more details.
Also, if you are intending to update an existing list, make sure to get the current count of items before making any changes. For instance, a getItemCount() on the adapter should be called to record the first index that will be changed.
// record this value before making any changes to the existing list
int curSize = itemArrayAdapter.getItemCount();
// update the existing list
newItems.add(Item(....img_bitmap));
// curSize should represent the first element that got added
// newItems.size() represents the itemCount
itemArrayAdapter.notifyItemRangeInserted(curSize, newItems.size());

Categories

Resources