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

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!

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

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.

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

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

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

Related

Refresh ListView when item in adapter is removed

I have created a ListView of items that each one of them looks as follows:
In simplified code, It looks like this:
public class MyBAdapter extends ArrayAdapter {
private static class ViewHolder {
public ImageButton ib_Delete;
public ToggleButton tb_Status;
}
public MyBAdapter(Context context, ArrayList<MyB> aB) {
super(context, 0, aB);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final MyB b = getItem(position);
ViewHolder viewHolder;
if (convertView == null) {
viewHolder = new ViewHolder();
LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate( R.layout.item_b_myb, parent, false);
viewHolder.ib_DeleteB = convertView.findViewById( R.id.ib_DeleteB );
viewHolder.tb_Status = convertView.findViewById( R.id.tb_Status );
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
return convertView;
}
}
Now, I had like that once a user clicks on the ToggleButton of on the x button, that it will remove this item from the list and that the view will be updated on this screen.
How can I do it?
I saw that for this case notifyDataSetChanged() won't work.
Basically what I'm trying to do is to refresh the list without calling again the activity.
Thank you
You can try like this:
public static class MyBAdapter extends ArrayAdapter {
private ArrayList<String> aB;
private class ViewHolder {
public ImageButton ib_Delete;
public ToggleButton tb_Status;
public TextView text;
}
public MyBAdapter(Context context, ArrayList<String> aB) {
super(context,0, aB);
this.aB = aB;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
viewHolder = new ViewHolder();
LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate( R.layout.item_b_myb, parent, false);
viewHolder.ib_Delete = convertView.findViewById( R.id.ib_DeleteB );
viewHolder.tb_Status = convertView.findViewById( R.id.tb_Status );
viewHolder.text = convertView.findViewById( R.id.text );
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
viewHolder.tb_Status.setChecked(false);
}
viewHolder.text.setText(aB.get(position));
viewHolder.ib_Delete.setTag(position);
viewHolder.ib_Delete.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if(viewHolder.tb_Status.isChecked()){
delete((Integer)view.getTag());
}
}
});
return convertView;
}
private void delete(int position) {
aB.remove(position);
notifyDataSetChanged();
}
#Override
public int getCount() {
return aB.size();
}
}

ExpandableListView.getGroupView() convertView arg null in fragment

Im new to android programming, the current architecture of my application is a Main Activity with a Fragment that fills the display. I am trying to create an ExpandableListView inside the Fragment which also fills the display. Everything works as i am expecting it, i think, however I cannot populate either the Child View or Group View.
In my Fragment Java class file i have;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
ExpandableListView lv = (ExpandableListView) getView().findViewById(R.id.list);
lv.setAdapter(new ParentLevel(null));
}
ParentLevel which extends BaseExpandableListAdapter has;
#Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent)
{
return convertView;
}
convertView returns null, but parent returns the ExpandableListView in my layout file.
I'm not sure how to work with this or what to do next, if i wanted to just have a simple TextView, for a title, and a the group arrow what would i do?
I plan to use an enum for the data, which I am already inspecting for getGroupCount() and getChildCount()
I was able to solve my problem with this peice of code;
private final LayoutInflater inf;
private FragmentActivity activity;
public ParentLevel(FragmentActivity act, MenuItem current) {
activity = act;
inf = LayoutInflater.from(act);
};
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent)
{
ViewHolder holder;
if (convertView == null) {
convertView = inf.inflate(R.layout.expandable_list_item, parent, false);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.text.setText(activity.getString(siblings.get(groupPosition).getName()));
return convertView;
}
and this class;
private class ViewHolder {
TextView text;
}

ArrayAdapter getView returns NullPointerException -

I'm new to android, I've tried many solutions but nothing works!
Can you please see what's wrong with my code:
I get NullPointerException in holder.CardContent.setText(card.getString("content"));
I've tried to change layoutInflater from null to parent, false
The ids are not the false one!
CardAdapter.java
public class CardAdapter extends ArrayAdapter<ParseObject> {
protected Context mContext;
protected List<ParseObject> mCards;
public CardAdapter(Context context, List<ParseObject> cards) {
super(context, R.layout.card_item, cards);
mContext = context;
mCards = cards;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if(convertView == null) {
convertView = LayoutInflater.from(mContext).inflate(R.layout.card_item, null);
holder = new ViewHolder();
holder.CardContent = (TextView) convertView.findViewById(R.id.cardText);
} else{
holder = (ViewHolder) convertView.getTag();
}
ParseObject card = mCards.get(position);
holder.CardContent.setText(card.getString("content")); // <-- NPE here.
return convertView;
}
private static class ViewHolder{
TextView CardContent;
}
}
Logcat:
at com.kardapps.lifehacks.activities.CardAdapter.getView(CardAdapter.java:64)
at android.widget.AbsListView.obtainView(AbsListView.java:2255)
at android.widget.ListView.measureHeightOfChildren(ListView.java:1263)
at android.widget.ListView.onMeasure(ListView.java:1175)
.......
The problem is that holder is null at least for the beginning.
card could be null too but its not clear at the moment.
Although the adapter recycling logic is right you have forgotten to setTag to the View.
The logic of the recycling is that the way you scroll you save (using setTag) the already read/seen data to the view (here convertView).
Then each time you pass the same position you use setTag to retrieve them instead of recreating them (LayoutInflater.from...)
#Override
public View getView(int position, #Nullable View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
// This is not an error but using parent you avoid Lint warnings
convertView = LayoutInflater.from(mContext).inflate(R.layout.card_item, parent);
holder = new ViewHolder();
holder.CardContent = (TextView) convertView.findViewById(R.id.cardText);
convertView.setTag(holder); // <-- As suggested for improvement
} else {
holder = (ViewHolder) convertView.getTag();
}
ParseObject card = mCards.get(position);
holder.CardContent.setText(card.getString("content")); // <-- NPE here.
// convertView.setTag(holder); // <-- This line is missing
return convertView;
}
You need to set convertView's tag to the holder in your if-block.
if (convertView == null) {
convertView = LayoutInflater.from(mContext).inflate(R.layout.card_item, null);
holder = new ViewHolder();
holder.CardContent = (TextView) convertView.findViewById(R.id.cardText);
// Add this line
convertView.setTag(holder);
}
You can see that in the else-block, you're retrieving the tag and casting it to ViewHolder. If you haven't set the tag, getTag() returns null, causing the Exception.
See this code:
if(convertView == null){
convertView = LayoutInflater.from(mContext).inflate(R.layout.card_item, null);
holder = new ViewHolder();
holder.CardContent = (TextView) convertView.findViewById(R.id.cardText);
}
else{
holder = (ViewHolder) convertView.getTag();
}
ParseObject card = mCards.get(position);
holder.CardContent.setText(card.getString("content"));
Two possibilities:
if convertView is not null, you are just initialising holder object and not cardContent.
mCards.get(position); returns null

How to get view for an item in listview in android?

Is it possible to get an item view based on its position in the adapter and not in the visible views in the ListView?
I am aware of functions like getChildAt() and getItemIdAtPosition() however they provide information based on the visible views inside ListView. I am also aware that Android recycles views which means that I can only work with the visible views in the ListView.
My objective is to have a universal identifier for each item since I am using CursorAdapter so I don't have to calculate the item's position relative to the visible items.
Here's how I accomplished this. Within my (custom) adapter class:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = null;
if (convertView == null) {
LayoutInflater inflater = context.getLayoutInflater();
view = inflater.inflate(textViewResourceId, parent, false);
final ViewHolder viewHolder = new ViewHolder();
viewHolder.name = (TextView) view.findViewById(R.id.name);
viewHolder.button = (ImageButton) view.findViewById(R.id.button);
viewHolder.button.setOnClickListener
(new View.OnClickListener() {
#Override
public void onClick(View view) {
int position = (int) viewHolder.button.getTag();
Log.d(TAG, "Position is: " +position);
}
});
view.setTag(viewHolder);
viewHolder.button.setTag(items.get(position));
} else {
view = convertView;
((ViewHolder) view.getTag()).button.setTag(items.get(position));
}
ViewHolder holder = (ViewHolder) view.getTag();
return view;
}
Essentially the trick is to set and retrieve the position index via the setTag and getTag methods. The items variable refers to the ArrayList containing my custom (adapter) objects.
Also see this tutorial for in-depth examples. Let me know if you need me to clarify anything.
See below code:
public static class ViewHolder
{
public TextView nm;
public TextView tnm;
public TextView tr;
public TextView re;
public TextView membercount;
public TextView membernm;
public TextView email;
public TextView phone;
public ImageView ii;
}
class ImageAdapter extends ArrayAdapter<CoordinatorData>
{
private ArrayList<CoordinatorData> items;
public FoodDriveImageLoader imageLoader;
public ImageAdapter(Context context, int textViewResourceId,ArrayList<CoordinatorData> items)
{
super(context, textViewResourceId, items);
this.items = items;
}
public View getView(int position, View convertView, ViewGroup parent)
{
View v = convertView;
ViewHolder holder = null;
if (v == null)
{
try
{
holder=new ViewHolder();
LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
imageLoader = new FoodDriveImageLoader(FoodDriveModule.this);
v = vi.inflate(R.layout.virtual_food_drive_row, null);
//System.out.println("layout is null.......");
holder.nm = (TextView) v.findViewById(R.id.name);
holder.tnm = (TextView) v.findViewById(R.id.teamname);
holder.tr = (TextView) v.findViewById(R.id.target);
holder.re = (TextView) v.findViewById(R.id.received);
holder.membercount = new TextView(FoodDriveModule.this);
holder.membernm = new TextView(FoodDriveModule.this);
holder.email = new TextView(FoodDriveModule.this);
holder.phone = new TextView(FoodDriveModule.this);
holder.ii = (ImageView) v.findViewById(R.id.icon);
v.setTag(holder);
}
catch(Exception e)
{
System.out.println("Excption Caught"+e);
}
}
else
{
holder=(ViewHolder)v.getTag();
}
CoordinatorData co = items.get(position);
holder.nm.setText(co.getName());
holder.tnm.setText(co.getTeamName());
holder.tr.setText(co.getTarget());
holder.re.setText(co.getReceived());
holder.ii.setTag(co.getImage());
imageLoader.DisplayImage(co.getImage(), FoodDriveModule.this , holder.ii);
if (co != null)
{
}
return v;
}
}
A better option is to identify using the data returned by the CursorAdapter rather than visible views.
For example if your data is in a Array , each data item has a unique index.
Here, Its very short described code, you can simple re-use viewholder pattern to increase listview performance
Write below code in your getview() method
ViewHolder holder = new ViewHolder();
if (convertView == null) {
convertView = layoutInflater.inflate(R.layout.skateparklist, null);
holder = new ViewHolder();
holder.headlineView = (TextView) convertView
.findViewById(R.id.textView1);
holder.DistanceView = (TextView) convertView
.findViewById(R.id.textView2);
holder.imgview = (NetworkImageView) convertView
.findViewById(R.id.imgSkatepark);
convertView.setTag(holder); //PLEASE PASS HOLDER AS OBJECT PARAM , CAUSE YOU CAN NOY PASS POSITION IT WILL BE CONFLICT Holder and Integer can not cast
//BECAUSE WE NEED TO REUSE CREATED HOLDER
} else {
holder = (ViewHolder) convertView.getTag();
}
// your controls/UI setup
holder.DistanceView.setText(strDistance);
......
return convertview
Listview lv = (ListView) findViewById(R.id.previewlist);
final BaseAdapter adapter = new PreviewAdapter(this, name, age);
confirm.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
View view = null;
String value;
for (int i = 0; i < adapter.getCount(); i++) {
view = adapter.getView(i, view, lv);
Textview et = (TextView) view.findViewById(R.id.passfare);
value=et.getText().toString();
Toast.makeText(getApplicationContext(), value,
Toast.LENGTH_SHORT).show();
}
}
});

Dialog, Adapter and Data change

I have a custom alert dialog with this code adapter
adapter_nomi = new ArrayAdapter<nomi>(context, R.layout.dialog_lista_nomi, R.id.r_dialog_nomi, list_nomi){
#Override
public View getView(int position, View convertView,ViewGroup parent) {
ViewHolder holder = null;
final String indirizzo;
if(convertView==null){
LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.row_dialog_nomi, null);
holder = new ViewHolder();
holder.nome = (TextView)convertView.findViewById(R.id.r_dialog_nomi);
convertView.setTag(holder);
}else{
holder = (ViewHolder)convertView.getTag();
}
nomi item = getItem(position);
holder.nome.setText(item.get("nominativo"));
return convertView;
}
};
but when list_nomi change and I call showDialog it's not refresh. I have also put into onPrepareDialog the code adapter_nomi.notifyDataSetChange(); but it don't work.....why ?

Categories