57

I am getting following warning in eclipse:

Unconditional layout inflation from view adapter: Should use View Holder pattern (use recycled view passed into this method as the second parameter) for smoother scrolling.

on:

convertView = vi.inflate(R.layout.activity_friend_list_row, parent, false);

I have a base adapter with a CheckBox implemented and I have added a tag to make the CheckBox work.

Here is the code:

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

    ViewHolder mViewHolder;
    mViewHolder = new ViewHolder();
    LayoutInflater vi = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    convertView = vi.inflate(R.layout.activity_friend_list_row, parent, false);

    mViewHolder.cb = (CheckBox) convertView.findViewById(R.id.checkBox);

    convertView.setTag(mViewHolder);

    if (InviteFriends.isChecked[position] == true)
    {
        mViewHolder.cb.setChecked(true);
    }
    else
    {
        mViewHolder.cb.setChecked(false);
    }

    mViewHolder.cb.setOnCheckedChangeListener(new OnCheckedChangeListener() 
    {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean ischecked) 
        {
            if (buttonView.isChecked())
            {
                InviteFriends.isChecked[position] = true;

            }
            else
            {
                InviteFriends.isChecked[position] = false;
            }
        }
    });

    TextView friendsname  = (TextView) convertView.findViewById(R.id.friendsName); // title
    ImageView thumb_image = (ImageView) convertView.findViewById(R.id.list_image); // thumb image

    HashMap<String, String> song = new HashMap<String, String>();
    song = data.get(position);

    // Setting all values in listview
    friendsname.setText(song.get(InviteFriends.KEY_DISPLAY_NAME));
    imageLoader.DisplayImage(song.get(InviteFriends.KEY_IMAGEPROFILE_URL), thumb_image);


    return convertView;
}

The results are coming up properly. How do I fix this warning? I am not able to get a solution for this yet?

Thanks!

ישו אוהב אותך
  • 28,609
  • 11
  • 78
  • 96
TheDevMan
  • 5,914
  • 12
  • 74
  • 144

5 Answers5

66

Try this

static class ViewHolder {

    private TextView friendsname;
    private ImageView thumb_image;
    private CheckBox cb;

}
public View getView(final int position, View convertView, ViewGroup parent) {

    ViewHolder mViewHolder = null;
    HashMap<String, String> song = null;

    if (convertView == null) {

        song = new HashMap <String, String>();
        mViewHolder = new ViewHolder();

        LayoutInflater vi = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        convertView = vi.inflate(R.layout.activity_friend_list_row, parent, false);
        mViewHolder.friendsname = (TextView) convertView.findViewById(R.id.friendsName); // title
        mViewHolder.thumb_image = (ImageView) convertView.findViewById(R.id.list_image); // thumb image


        mViewHolder.cb = (CheckBox) convertView.findViewById(R.id.checkBox);

        convertView.setTag(mViewHolder);
        mViewHolder.cb.setTag(data.get(position));

        mViewHolder.cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean ischecked) {

                InviteFriends.isChecked[position] = buttonView.isChecked();

            }
        });

    } else {

        mViewHolder = (ViewHolder) convertView.getTag();

    }

    song = mViewHolder.cb.getTag();

    mViewHolder.friendsname.setText(song.get(InviteFriends.KEY_DISPLAY_NAME));
    mViewHolder.imageLoader.DisplayImage(song.get(InviteFriends.KEY_IMAGEPROFILE_URL), thumb_image);
    mViewHolder.cb.setChecked(InviteFriends.isChecked[position]);

    return convertView;
}
ndsmyter
  • 6,535
  • 3
  • 22
  • 37
Sonali8890
  • 2,005
  • 12
  • 16
  • @ user88 : This solved the issue with few changes to suit my requirement. But what would have happened if I had the solution that just showed warning.. I still got the results. – TheDevMan Aug 19 '14 at 11:26
  • 21
    In case it was hard to tell, the key to the code above is that you need to check for "if (convertView == null)" before inflating the row again. – beyondtheteal May 01 '15 at 12:12
  • 9
    Please tell us *how* this fixes the problem and *what* it does. [How to answer](http://stackoverflow.com/help/how-to-answer) helpcenter. –  Feb 12 '17 at 19:29
  • Some official docs on this technique: https://developer.android.com/training/improving-layouts/smooth-scrolling.html#ViewHolder – Trevor Robinson Feb 13 '17 at 23:14
  • this answer is incorrect because the song data is always the same by saving it to the tag. Please see a more correct solution in my answer: https://stackoverflow.com/a/51073730/4758255 – ישו אוהב אותך Jun 28 '18 at 02:36
  • @Sonali8890, The same title of this question warning was appearing for me too and your answer helped me to remove that warning, by implementing the viewholder as you suggested. Thanks and Up vote. – Rushikant Pawar Feb 17 '20 at 06:50
37

you should init the convert view only if it is null

these lines

LayoutInflater vi = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

convertView = vi.inflate(R.layout.activity_friend_list_row, parent, false);
// [...] the rest of initialization part
// [...] some changes that must be done at refresh
return convertView;

should look like this:

if (convertView == null) {
    LayoutInflater vi = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    convertView = vi.inflate(R.layout.activity_friend_list_row, parent, false);
    // [...] the rest of initialization part
}
// [...] some changes that must be done at refresh
return convertView;

the goal is to recycle the already existing view in that list, not to init it each time you display it when scrolling the list for example.

ungalcrys
  • 5,242
  • 2
  • 39
  • 23
  • Thanks for sharing this. To understand something, when I scroll down my custom list view, it calls every time the getView? So the getView is representing each custom row of the list...Am I follow well? – Mohammed AlBanna Dec 08 '15 at 15:15
7

Unconditional layout inflation from view adapter: Should use View Holder pattern (use recycled view passed into this method as the second parameter) for smoother scrolling.

It means that you need to use View Holder pattern in your Adapter. The point of using View Holder is to reusing the views because inflating and using findViewById are slow.

When you're using the following code:

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

    ViewHolder mViewHolder;
    mViewHolder = new ViewHolder();
    LayoutInflater vi = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    convertView = vi.inflate(R.layout.activity_friend_list_row, parent, false);
    mViewHolder.cb = (CheckBox) convertView.findViewById(R.id.checkBox);
    convertView.setTag(mViewHolder);

    ...

    return convertView;

} 

you're not reusing the views but instead you always create new views.

You need to change your code to something like this (please check the comment):

// class for holding the cached view
static class ViewHolder {
   TextView tvFriendsName;
   ImageView imvThumbImage;
   CheckBox cbInviteFriend;
}

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

    // holder of the views to be reused.
    ViewHolder viewHolder;

    // get data based on the position
    HashMap<String, String> song = data.get(position);

    // if no previous views found
    if (convertView == null) {
       // create the container ViewHolder
       viewHolder = new ViewHolder();

       // inflate the views from layout for the new row
       LayoutInflater inflater = LayoutInflater.from(parent.getContext());
       convertView = inflater.inflate(R.layout.rowlayout, parent, false);

       // set the view to the ViewHolder.
       viewHolder.cbInviteFriend = convertView.findViewById(R.id.checkBox);
       viewHolder.tvFriendsName  = convertView.findViewById(R.id.friendsName);
       viewHolder.imvThumbImage = convertView.findViewById(R.id.list_image); 

       // save the viewHolder to be reused later.
       convertView.setTag(viewHolder);
    } else {
       // there is already ViewHolder, reuse it.
       viewHolder = (ViewHolder) convertView.getTag();
    }

    // now we can set populate the data via the ViewHolder into views
    viewHolder.tvFriendsName.setText(song.get(InviteFriends.KEY_DISPLAY_NAME));
    imageLoader.DisplayImage(song.get(InviteFriends.KEY_IMAGEPROFILE_URL), viewHolder.imvThumbImage);
    viewHolder.cbInviteFriend.isChecked(InviteFriends.isChecked[position]);

    return convertView;
}
ישו אוהב אותך
  • 28,609
  • 11
  • 78
  • 96
  • How would you implement an OnClickListener with this approach? As I have tried to attach a listener to the ListView and ListView item view in order to change the background color, however, when scroll down items that haven`t been clicked change their color as well. – alex_z Jan 17 '19 at 10:46
  • Thank you. I think your explanation was more crisp and clear than the most upvoted solution. – Shubham Nanche Mar 16 '22 at 12:05
4

My suggestion is try to use convertView = vi.inflate(R.layout.activity_friend_list_row, null); insted of convertView = vi.inflate(R.layout.activity_friend_list_row, parent, false); this may help you.

:- okey.. insted of accessing like this TextView friendsname = (TextView) convertView.findViewById(R.id.friendsName); // title ImageView thumb_image = (ImageView) convertView.findViewById(R.id.list_image); // thumb image you have to use viewholder class in your adapter

for example

static class ViewHolder {
    public TextView text;
    public ImageView image;
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
    View rowView = convertView;
    // reuse views
    if (rowView == null) {
      LayoutInflater inflater = context.getLayoutInflater();
      rowView = inflater.inflate(R.layout.rowlayout, null);
      // configure view holder
      ViewHolder viewHolder = new ViewHolder();
      viewHolder.text = (TextView) rowView.findViewById(R.id.TextView01);
      viewHolder.image = (ImageView) rowView
          .findViewById(R.id.ImageView01);
      rowView.setTag(viewHolder);
    }

    // fill data
    ViewHolder holder = (ViewHolder) rowView.getTag();
    String s = names[position];
    holder.text.setText(s);
    if (s.startsWith("Windows7") || s.startsWith("iPhone")
        || s.startsWith("Solaris")) {
      holder.image.setImageResource(R.drawable.no);
    } else {
      holder.image.setImageResource(R.drawable.ok);
    }

    return rowView;
  }
Jithu
  • 1,478
  • 1
  • 13
  • 21
  • @ Jithu: Thanks for the quick reply. I am still getting the warning! – TheDevMan Aug 19 '14 at 10:50
  • 1
    `vi.inflate(R.layout.activity_friend_list_row, null);` should not be used. Read [this](http://www.doubleencore.com/2013/05/layout-inflation-as-intended/) – DroidDev Dec 08 '14 at 06:54
0

What i have done is created a BaseSpinnerAdapter class and reusing it if needed

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import androidx.annotation.LayoutRes

/**
 * @param T Model class
 * @param VH ViewHolder class, which holds the view to reuse it
 * */
abstract class BaseSpinnerAdapter<T, VH>(context: Context?, private val items: ArrayList<T>) :
    BaseAdapter() {

    private var inflater: LayoutInflater = LayoutInflater.from(context)

    /** use viewHolder pattern to reusing the views
     * @param p0 current view position
     * @param p1
     * @param viewGroup
     * */
    override fun getView(p0: Int, p1: View?, viewGroup: ViewGroup?): View {
        var pClone = p1
        val viewHolder: VH

        if (pClone == null) {
        pClone = inflater.inflate(setSpinnerItemLayout(), viewGroup, false)
        viewHolder = createViewHolder(pClone)
        pClone?.tag = viewHolder
    } else {
        viewHolder = pClone.tag as VH

    }
        getView(viewHolder, p0)

        return pClone!!
    }


    override fun getItem(p0: Int): T {
        return items[p0]
    }

    override fun getItemId(p0: Int): Long {
        return p0.toLong()
    }

    override fun getCount(): Int {
        return items.size
    }

    @LayoutRes
    abstract fun setSpinnerItemLayout(): Int

    abstract fun getView(viewHolder: VH, position: Int)

    abstract fun createViewHolder(view: View?): VH


}

Here is an example How you can use BaseSpinnerAdapter in your implementation. I believe that there is no need to detail out the code description.

    class SpinnerImplAdapter(context: Context?, private val items: ArrayList<AnyModelClass>) : BaseSpinnerAdapter<AnyModelClass, SpinnerImplAdapter.ViewHolderImp>(context, items) {
    
    
        override fun setSpinnerItemLayout(): Int {
            return R.layout.spinner_item
        }
    
        override fun createViewHolder(view: View?): ViewHolderImp {
            return ViewHolderImp(view)
        }
    
        override fun getView(viewHolder: ViewHolderImp, position: Int) {
            val model = items[position]
            viewHolder.textView?.text = "" // model.etc get anything you want
        }
    
    
/**
 * I have used kotlin extension to get the viewId, 
 * if you are not using then simply call the view.findViewById(R.id.yourView)
 * */
        class ViewHolderImp(view: View?) {
            val textView: TextView? = view?.textView
        }
    
    
    }
shahid17june
  • 1,441
  • 1
  • 10
  • 15