Unconditional layout, inflation from view adapter: Should use View Holder pattern

JavaAndroidLayout Inflater

Java Problem Overview


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!

Java Solutions


Solution 1 - Java

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;
}

Solution 2 - Java

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.

Solution 3 - Java

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;
  }

Solution 4 - Java

> 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;
}

Solution 5 - Java

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
        }
    
    
    }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTheDevManView Question on Stackoverflow
Solution 1 - JavaSonali8890View Answer on Stackoverflow
Solution 2 - JavaungalcrysView Answer on Stackoverflow
Solution 3 - JavaJithuView Answer on Stackoverflow
Solution 4 - Javaישו אוהב אותךView Answer on Stackoverflow
Solution 5 - Javashahid17juneView Answer on Stackoverflow