Update a TextView in all Adapters - most efficient way - android

I have a list of items. Each of them has a set of data displayed with TextViews. This data remains mostly unchanged. But I have a distance field, which I would like to update whenever I get new lock from location provider.
The question is: Should I just update my data and call notifyDataSetChanged() on my Adapter or is there a more efficient way?
Seems very expensive to reload all the lists (I have several of them in a ViewPager) just because one TextView in each list item needs to be updated.
Here is my getView() from my adapter. It might help:
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.eventrow, parent, false);
// Creates a ViewHolder and store references to the two children views
// we want to bind data to.
holder = new ViewHolder();
holder.title = (TextView) convertView.findViewById(R.id.eventTitle);
holder.distance = (TextView) convertView.findViewById(R.id.eventDistance);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.title.setText(((EventItem) getItem(position)).getTitle());
holder.distance.setText(String.valueOf(((EventItem) getItem(position)).getDistance()));
return convertView;
}
I also thought about directly referencing the holder.distance but it seems like a bad idea to do it outside getView().

The correct way to modify your data is to change your list item and then call notifyDataSetChanged().
The only alternative that comes to my mind is to set again the adapter on the list view which is way more expensive. There are no other ways.
So the answer is: you HAVE to go through notifyDataSetChanged().

Related

Does ListView automatically recycle views?

I'm new to android programming, and I've been reading a lot about it lately. One of the features of ListView, if I understood it right, is that it recycle views and just replaces it with new data when an item is off the screen.
And just a few minutes ago, I was reading up about endless scrolling, and RecyclerView has been one of the popular choices to implement such a feature. So I looked up RecyclerView, and in this video, it is mentioned that RecyclerView recycles a view automatically to reuse it for new data (as a way to contrast its difference with ListView).
Did I misunderstand ListView about its recycling mechanism? Or if it does recycle, how do you actually implement (or how do you know you are implementing) it?
RecyclerView does recycling automatically. In order to make ListView recycle items you will need to do this modification inside of adapter class.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
//brand new
convertView = LayoutInflater.from(mContext).inflate(R.layout.days_list_item, null);
holder = new ViewHolder();
// below is variables that will be different in your case
holder.numberOfDays = (TextView) convertView.findViewById(R.id.eventDays);
holder.sinceOrUntil = (TextView) convertView.findViewById(R.id.eventType);
holder.eventTitle = (TextView) convertView.findViewById(R.id.eventTitle);
holder.daysText = (TextView) convertView.findViewById(R.id.DaysText);
convertView.setTag(holder);
}
else {
//reusing item
holder = (ViewHolder) convertView.getTag();
}
// rest of the code
}
For more details refer to this link.

What is the difference between 2 code of Listview adapter getView method

I know recycle view is new but I want to know what is difference in 2 codes in list view. I have already tried to search a lot but not get specific answer. I know First one is more faster then the second because of memory consumption but why second code is slow then the first one what is the internal process can any one enplane it.
This is the first Code
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
final HashMap<String ,String > item = lst.get(position);
ViewHolderItem viewHolder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.shadow_request_row, parent, false);
viewHolder = new ViewHolderItem();
viewHolder.title = (TextView)convertView.findViewById(R.id.item_name);
viewHolder.msg = (TextView)convertView.findViewById(R.id.message);
convertView.setTag(viewHolder);
}else{
viewHolder = (ViewHolderItem) convertView.getTag();
}
viewHolder.title.setText(item.get(Const.USERNAME));
viewHolder.msg.setText(item.get(Const.GET_MESSAGE));
return convertView;
}
This is the Second Code
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
final HashMap<String ,String > item = lst.get(position);
ViewHolderItem viewHolder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.shadow_request_row, parent, false);
viewHolder = new ViewHolderItem();
viewHolder.title = (TextView)convertView.findViewById(R.id.item_name);
viewHolder.msg = (TextView)convertView.findViewById(R.id.message);
}
viewHolder.title.setText(item.get(Const.USERNAME));
viewHolder.msg.setText(item.get(Const.GET_MESSAGE));
return convertView;
}
The second example is missing the part about saving the viewHolder as the tag of the created view if the view is just being inflated and reusing the viewHolder if the view already exists.
List View items are re created whenever it wants. (This happens when you scroll up and down). Whenever a ListView needs to re create an item it calls the getView() of the adapter with the required position. Inside the getView() method you have the logic to generate the item View required for that position.
The method findViewById() that you use to find a View inside the XML, is CPU extensive. You might see a considerable lag if your XML contains a long sequence of children and the getView() contains lots of findViewById() calls.
This is where a ViewHolder comes handy. A ViewHolder is a class that can hold the View items. You can use the already created ViewHolder objects instead of calling findViewById() every time.
To make use of this you have to save a ViewHolder object associated with a particular position. You do it like this.
ViewHolder viewHolder;
if(convertView==null){
//the view is created for the first time
//you have to make the View HOlder object here
viewHOlder=new ViewHOlder(convertView);
//ViewHOlder constructor can find the required view elements and store it in variables
//now you have to save this View Holder object for future reference
//you save it as a tag
convertView.setTag(viewHolder);
}
Now you have a defined View Holder for the specific item position. Here is how to re use it.
When the ListView adapter wnats to re use it the convertView given to getItem() is not null. So the re use occurs in the else statement of the above if.
else{
//you already have a pre created View holder. Retrieve it.
viewHOlder=(ViewHolder)convertView.getTag();
//now you can get access to your View elements easily
}
In you second example, you create the ViewHOlder but you never re use it. So it makes not improvement.

Saving user input while adding/removing ListView items in Android

I have a ListView which multiple contains checkboxes for user input. I have two buttons add and remove to add and remove list items respectively.
I am using BaseAdapter for ListView and notifying the ListView using notifyDataSetChanged()
Anytime I add/remove the items. The user input in the list gets cleared. I think it's because the list is getting rebuilt everytime. Any ideas on how to keep the user input while add/remove the items in listView ?
in listview you have only one option is to use notifyDatasetChanged() with adapter. just dont clear your whole list just do add/remove and then notify adapter or use Recycclerview instead.
With recyclerview you have many options like notifyDataInserted, notifyDataRemoved, range inserted, range removed etc.
You can find a good example here
i think your programme is reading getView() function again. see the below code to avoid it, it is copy pasted but it may give you some idea-
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item_icon_text, null);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.text.setText(DATA[position]);
holder.icon.setImageBitmap((position & 1) == 1 ? mIcon1 : mIcon2);
return convertView;
}
you can refer this link for more info-0
Android, how to stop reading getView() function again, if the content is already downloaded in the ListView

Holder pattern and convert view

I have learned earlier about great approach to increase performance - Holder Pattern. This is good idea to speed up UI and animation.
It is clearly why and how to use it.
I have used it a lot , but now I am little bit confused about this.
When getView method is called it has three arguments one is converView. As I undertand it is previously inflated view of list item, so the are some questions about this.
If it is previously inflated view, why not just to use it, return it from method, of course check to null before.
How does this implemented,listview class has private array or another data structure that holds all inflated views ?
Why this feature is not implemented in adapters ?
Thanks in advance.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder =(ViewHolder) convertView.getTag();
}
If you would just use the convertView, you would need to get hold of your views with findViewById(). This is exactly what the ViewHolder pattern is trying to avoid. findViewById() is a surprisingly expensive method and can slow down your app, especially if you constantly call it when scrolling through lists.
Listviews reuse the layouts of the child items, to avoid having to inflate the same views over and over again.
Most adapters were already available to developers before people came up with the ViewHolder pattern. The latest new list view, RecyclerView, has an adapter that enforces the use of the ViewHolder pattern.
You can't call ViewHolder holder =(ViewHolder) convertView.getTag(); at the first line of getView. Because convertView could be null. Try again like this:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHoldler holder = null;
if (convertView == null) {
convertView = LayoutInflater.from(ctx).inflate(
R.layout.frag_home_gridview_item, null, false);
holder = new ViewHoldler();
holder.iv = (ImageView) convertView
.findViewById(R.id.gridview_item_label);
holder.tv = (TextView) convertView
.findViewById(R.id.gridview_item_name);
convertView.setTag(holder);
} else {
holder = (ViewHoldler) convertView.getTag();
}
holder.tv.setText(getItem(position));
holder.iv.setImageResource(this.ids[position]);
return convertView;
}
private class ViewHoldler {
ImageView iv;
TextView tv;
}
Because you most likely are not using same views. Say you have a row which has a TextView in it. The convertview is one of the recycled views and it's textview may be displaying different information that it should be.
It does keep as many views as there are visible, once you scroll down the top view get's recycled and you come back to answer 1.
I don't understand, your code is from the BaseAdapter class.

ListView adapter gets confused when convertView is recycled

I have a straight forward BaseAdapter for my ListView. It downloads a JSON feed and displays the data in the rows. There is a ViewHolder which contains the views and a data object called "Story". Everything works just fine.
However, after some scrolling of longer lists, I notice two things.
1) My log shows that the adapter is reloading the feed when scrolling further down. This is strange, as I put the whole JSON array into a variable, so why does it have to reload?
2) More importantly, after some scrolling back and forth, the rows contain the wrong "Story" objects. Here are the relevant parts of the getView routine:
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
Story story = stories.get(position);
if (convertView == null) {
//create holder
holder = new ViewHolder();
convertView = inflator.inflate(R.layout.story_list_item, parent, false);
holder.titleView = (TextView) convertView.findViewById(R.id.story_list_title);
holder.dateView = (TextView) convertView.findViewById(R.id.story_list_date);
holder.story = story;
holder.imageView = (ImageView) convertView.findViewById(R.id.story_list_image);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
// configure the view
holder.titleView.setText(story.title);
return convertView;
}
Simple enough. Now the strange thing is that I can fix the problem by eliminating the if statement if (convertView == null) (and, I presume, eliminating the row recycling as well).
But will I not run into memory problems this way? Why does the plain vanilla version not work?
Thanks for your help.
Regards,
S
You are aware that you're only assigning
holder.story = story
when convertView == null ? Consider moving holder.story = story to just after your convertView if-case and it should work a lot better. Btw, do you even need to store the "story" inside your view holder? Typically that pattern should only be used to store Views and view state information, not the data of the actual position.

Categories

Resources