App Development

Performance ListViews

blog-featured-image performance-list-views bc

Lists can be seen in almost any app. It’s an easy way to show items such as recipes, contacts, or any type of category really. It only makes sense that Android should have a built-in way to show this type of data representation. The latest implementation is the RecyclerView. It’s built for efficiency as it reuses views instead of recreating them every time a row comes on screen. Before the RecyclerView we had the ListView, which is still used widely today. While the ListView also recycles views, it continues to hold a place as one of the most misconfigured views in Android today. We know this topic has been written about many times before, but we’re bringing it up on the blog today because we still get many applicants who configure them incorrectly.

Here is what the standard novice ArrayAdapter for a ListView looks like:

@Override
    public View getView(int position, View convertView, ViewGroup parent) {


        LayoutInflater inflater = (LayoutInflater) context
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View rowView = inflater.inflate(R.layout.view_test_row, parent, false);


        TextView testName = (TextView)rowView.findViewById(R.id.text_view_test_name);
        TextView testDesc = (TextView)rowView.findViewById(R.id.text_view_test_desc);


        //modify TextViews, in some arbitrary way


        return rowView;
    }

Writing an AdapterView this way is not an issue when your list can be shown on the screen all at once, but then you could have created a basic view and avoided the hassle of an ArrayAdapter altogether right? When using a ListView we plan on showing a large list, often one with complex views. This is where performance takes a hit. The above way is costly to performance. When a user scrolls, each view is inflating and calling findViewById() every time. When findViewById() is called, the View Hierarchy is traversed until the proper Id is found. It does this for each sub-view you have to find! And the quicker the user scrolls, the more noticeable the sluggishness of the scrolling becomes. To remedy this, we can use a static class in combination with the convertView that we didn’t use earlier.

static class ViewHolder(){


        TextView testName;
        TextView testDesc;
        
        
}


    @Override
     public View getView(int position, View convertView, ViewGroup parent) {


        View rowView = convertView; //reference to one of the previous Views in the list that we can reuse.


        if(convertView == null) {


            LayoutInflater inflater = (LayoutInflater) context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            rowView = inflater.inflate(R.layout.view_test_row, parent, false);


            ViewHolder viewHolder = new ViewHolder();
            viewHolder.testName = (TextView) rowView.findViewById(R.id.text_view_test_name);
            viewHolder.testDesc = (TextView) rowView.findViewById(R.id.text_view_test_desc);


            rowView.setTag(viewHolder);
        }


        ViewHolder holder = (ViewHolder) rowView.getTag();
        
        
        //in real code these strings should be in res
        holder.testName.setText("Test"+position); 
        holder.testDesc.setText("This is number "+position);


        return rowView;
    }

But what is the convertView? Well, it allows the ListView to skip some of the setup needed to display the contents of a row. It is the view of a row that is now off the screen. We can reuse the view for the new row that will now be displayed. When the ListView is displayed in the beginning, everything happens like normal. Since there aren’t any views that can be reused, convertView is null. The view is inflated just like in our previous version, but the TextViews are found and the references are kept in a ViewHolder. We can then store the ViewHolder within the view by calling setTag(). This allows us to store data in the view which we can reference later, as shown in the latter half of the revised getView().

The changes we made might not seem like a huge impact, but as our views become more complicated and the number of them grows, inefficiencies will become noticeable. The last thing we want to do as developers is create an app with a negative user experience. Remember, just one sub-par ListView could be the life or death of an app, so let’s not take the chance!

Quickstart-Guide-to-Kotlin-Multiplatform

A Quick Start Guide to Kotlin Multiplatform

Kotlin Multiplatform, though still experimental, is a great up-and-coming solution...

Read the article