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
}
...
}
Related
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)
}
I have a tour list in android and I am trying to implementing add to favorites.
Favorite works for the list that is added in myTours but doesn't work for tours from search list.
This is the code:
private List<Tour> tourList;
holder.imgFavourite.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(tourList.get(position).getFav().equalsIgnoreCase("0")) {
tourList.get(position).setFav("1");
// listener.onFavourited(tourList.get(position), true);
holder.imgFavourite.setImageResource(R.drawable.faved);
} else {
tourList.get(position).setFav("0");
// listener.onFavourited(tourList.get(position), false);
holder.imgFavourite.setImageResource(R.drawable.not_faved);
}
}
});
Here when I click on Fav icon gets changed to faved. But if I search for different category and again come back to that category state doesn't persist.
Any help would be appreciated.
Listview/RecyclerView reuses views. If your list has 20 items and 4 are visible at one time, as you scroll, the views are reused to show the new visible views to save memory. So if you alter the view at position 5 and scroll down and scroll back up to position 5, the view that you changed in position 5 is not the same view that you are seeing after you scroll back up. Hence the view changes
To fix this, maintain a global variable in the adapter which stores the favorite position and in the onBindViewHolder, add a condition like
if(position = favPosition)
<Change to fav view>
else
<Change to Normal View>
The else condition is really important or else multiple views will have the Fav view
For recycler view always consider both cases only one case cause your list to change behaviour(like set same image for other list item because it reuses the cell for your view).the below mistake you are doing.
In bindviewholder you are getting data from model class and based on that you set resources for imageview.which is good.
But the problem is there no case for failing of the condition so you are losing the state and if you scroll down and again come to this item you lost your state or face behaviour that goes against your requirement.
So simply provide case for not matching your condition.
For instance, you have an adapter and in onBindViewHolder method you set OnClickListener to some views (and do some actions there depending on view position). You should assign final to position param of method onBindViewHolder so it could be accessible from onClick().
After changing dataset (remove or add item in list) you call onItemInserted or onItemRemoved and this really adds/removes a view in the recyclerview, BUT it does not refresh other viewitems so when you click on a neighbor viewitem it will open a screen or show data with invalid index. To avoid this I basically call notifyDatasetChanged to call onBind to all visible views and remove/add some views.
So how to refresh other views when you call notifyItemInserted/removed or how to work with these methots appropriately?
Assigning the position to a variable in onBindViewHolder will lead to an inconsistent state if items in the dataset are inserted or deleted without calling notifyDataSetChanged.
To use onItemInserted or onItemRemoved the data in the viewholder should remain consistent since it will not be redrawn and onClick would use this invalid position assigned before an item was added or removed.
For this and other use cases the RecyclerView.ViewHolder provides methods to access its position and id:
Use getAdapterPosition() or getItemId() to get valid positions and ids.
Also have a look on the other methods available in RecyclerView.ViewHolder.
So, the way I fix the problem I had is by changing the position into viewHolder.getAdapterPosition()
Cheers!
I advise you to add notifyItemRangeChanged after insert or remove list inside adapter. This work for my project.
Example in remove item :
public void removeItem (int pos) {
simpanList.remove(pos);
notifyItemRemoved(pos);
notifyItemRangeChanged(pos, simpanList.size());//add here, this can refresh position cmiiw
}
For future readers, this is what I do when inserting/removing in recyclerview
For example, my model class is CarsModel
In my adapter
ArrayList<CarsModel> carsModel;
In onBindViewHolder
CardModel model = carsModel.get(position);
When removing data in list using button in holder:
int position = holder.getAdapterPosition();
carsModel.remove(position);
notifyItemRemoved(position);
Then when inserting
carsModel.add(0, model);
notifyItemInserted(0);
or insert in last row
carsModel.add(carsModel.size() - 1 , model);
notifyItemInserted(carsModel.size()-1);
I'm creating kind of music player which has a listview with songs (having progress bar near currently playing song)
What is important is that I have an Item with views which can be changed from outside (in some handler)
public View getView(int i, View convertView, ViewGroup viewGroup) {
RelativeLayout result;
if (convertView == null) {
result = (RelativeLayout) inflater.inflate(R.layout.list_item, viewGroup, false);
} else {
result = (RelativeLayout) convertView;
}
...
ProgressBar progressBar = result.findViewById(R.id.progressBar)
...
if (i == currentSong) {
// saving to instance variable
this.currentlyPlayingProgressBar = progressBar;
} else {
progressBar.setVisibility(View.GONE);
}
...
return result;
}
(Code was changed to focus on my problem)
Btw currentSong can be changed from outside, adapter.notifyDataSetChanged() is being called in this case.
It seems like I'm using listView incorrectly, but I don't know the better way.
The main problem is that I need to have links to views to change them.
And the only way where I can get them is in getView method which reuses those view in a way only google developers can explain=(
First problem
This is all happening in Fragment which is just a part of a viewPager. when user scrolls of this fragment and then scrolls back then getView method is being called with some strange objects inside.. And I override currentlyPlayingProgressBar with this invalid value. Which causes the freeze of my statusbar. (it starts updating wrong view)
And I have no idea which view is it..
Second problem
I am reusing list items and this means that when user scrolls list view down - then sometimes he gets actually the same list item with the same progressBar.
This progressBar must be invisible but it's not (I think it's all because of my usage of currentlyPlayingProgressBar from outside)
Thanks in advance.
You can do this in two ways:
1) notifyDataSetChanged, which just resets entire ListView and assigns all visible list items again to views. notifyDataSetChanged is very expensive, since it makes entire list and view hierarchy to be recreated, layouts are measured, etc, which is slow. For frequent update of single list item, such as progress bar change, this is overkill.
2) Find view of particular listview item, and update only that one. We'll focus on this approach.
First you need to somehow identify which list view contains which list item. Common approach is to use View.setTag in your Adapter.getView, where setTag parameter is Object of your choice, may be same item as you return for Adapter.getItem.
So later you know which ListView child view has which item assigned.
Assuming you want to update particular item displayed in ListView, you have to iterate through ListView child views to find which view displays your item:
Object myItem = ...;
for(int i = list.getChildCount(); --i>=0; ){
View v = list.getChildAt(i);
Object tag = v.getTag();
if(tag==myItem) {
// found, rebind this item
bindItemToView(myItem, v);
break;
}
}
You must expect that ListView currently may not display your list item (is scrolled away).
From code above you see that it calls bindItemToView, which is your function to bind item to list view. You'd probably call same function to setup the item view in Adapter.getView.
You may also optimize it further, assuming you want to update only ProgressBar, then don't call bindItemToView, but update only ProgressBar in your listitem view (findViewById, setup values).
Hint: you can make it even more optimal by using ViewHolder approach. Then setTag would not contain your item object, but your ViewHolder object.
#Pointer null has also given very usefull aproach, but in your case I think u are updating the list which is not visible, in this case you have to set the tag from the adapter just like the list index and curresponding check if the list item exist between the last visible item or first visible item then update it else donot update..
I want to iterate a list of items into a ListView. This code below is not enough to iterate all the items into the list because of the weird behaviour of getChildCount() function which only returns the visible item count.
for (int i = 0; i < list.getChildCount(); i++) {
item = (View)list.getChildAt(i);
product = (Product)item.getTag();
// make some visual changes if product.id == someProductId
}
My screen displays 7 results and when there are more than 7 items into the list, it's not possible to access to the 8th item or so.. Only visible items..
Should I use ListIterator instead?
Thanks.
You need to customize your list adapter's getView() method, and put your check inside it to check if the current item's id matches:
Product product = items.get(position);
if(product.id == someProductId) {
//make visual changes
} else {
//reset visual changes to default to account for recycled views
}
Since typically only the visible items only exist at a specific time, getView is called whenever more need to be seen. They're created at that time, typically recycling views from the now-invisible items in the list (hence why you want to reset the changes if the criteria does NOT match).
So #kcoppock solved your first problem it seems you got another problem. How to update the view item? The android SMS application shows one way:
create your own list view item like this:
public class MyListItem extends RelativeLayout {
...
}
in your list view item layout file:
< MyListItem android:layout_width=.....>
...
</ MyListItem >
and in your code, when your view item can be seen, register MyListItem as a data changed listener(the data is up to you). I mean, when your data changed, then you can update the item directly.
Check out the SMS application source code to read more.
The number of views in the ListView(7) is different from the number of items in the adapter(which is more than 7) .Try to use a BaseAdapter.