Recently, I've been in and out of an android project for several reasons, and everytime I get back to it I find a lot of things that need refactoring like ListView
adapters.
In these adapters android uses view recycling to avoid having a list of 100 rows with a view instance for each row. View recycling basically instantiates only the necessary views to fill the screen, and when you start scrolling down it uses the same view with changed values.
While creating a custom adapter and extending from a SimpleAdapter
you need to override the getView
method which is where all the magic happens, this is where the specific layout for each row is inflated and the values are assigned to every widget.
So having said that, let me show you how the first version of our ListView
adapter looked like.
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Map<String, String> team = (HashMap<String, String>) getItem(position);
if (convertView == null) {
convertView = layoutInflater.inflate(R.layout.fragment_team, null);
}
((TextView) convertView.findViewById(R.id.team_name)).setText(team.get("name"));
((TextView) convertView.findViewById(R.id.team_count)). setText(team.get("count")));
return convertView;
}
By that moment we (my coworker and I) were just starting in Android and we thought this was a nice way to code our adapter. Then I read about the View Holder pattern and well… I realized it wasn't as nice as I had thought.
Even though it worked, the performance was not the best and that was because of all those findViewById
that made the adapter find the views every time the row was recreated.
Here is where the View Holder comes in, imagine you have a class like the following.
class ViewHolder {
TextView name;
TextView count;
}
In this class we will hold the references to each widget in the row so we don't have to fetch them again later and we get to save processor cycles.
And now we need to refactor our getView
method.
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Map<String, String> team = (HashMap<String, String>) getItem(position);
ViewHolder viewHolder = new ViewHolder();
if (convertView == null) {
convertView = layoutInflater.inflate(R.layout.fragment_team, null);
viewHolder.name = (TextView) convertView.findViewById(R.id.team_name);
viewHolder.count = (TextView) convertView.findViewById(R.id.team_count);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.name.setText(team.get("name"));
viewHolder.count.setText(team.get("count"));
return convertView;
}
When the convertView
argument is null
it means that the view would be created for the first time, so we need to inflate a layout, fetch the widgets inside the layout, hold them in the holder class, set this holder as the view tag and finally fill in the TextViews
with the given data.
When the view is being recycled we only need to fetch its tag, cast it to whatever our holder class is named and we are ready to access the widgets and change the values with the new data without fetching the views again, and this is how we save processor cycles.
Conclusion
Personally I like this pattern a lot. The code is more readable, performance is better, you'll get smooth scrolling for your lists and also if you ever need to access any widget again in some other method, you can just pass the holder as the argument and you have access to them instantly.
I hope you guys like this post, I'll be back soon with another post related to Android or RoR.
Thanks for reading.