How to properly implement an adapter for ListView

I have ListView

in Fragment

and I want to update the data in ListView

when I return from another Activity

. I've overwritten the method onResume()

in Fragment

, changed the data in Adapter

and called notifyDataSetChanged()

on Adpater

, but somehow it ListView

doesn't update. I suspect there is Adapter

something wrong with mine , but I just can't seem to find the error.

Here's my code Adpater

:

class ManualExceptionsListAdapter extends BaseAdapter {

    private LayoutInflater mInflater;
    private TextView mManualExceptions;
    SwitchCompat mSwitch;
    TextView name;
    final Context context = getActivity();
    final SharedPreferences mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
    int a;
    int ifUse = 0;

    ManualExceptionsListAdapter(LayoutInflater inflater) {
        mInflater = inflater;
    }

    @Override
    public int getCount() {
        return (mPermanentManualException.size()+mContactsExceptionNumber.size());
    }

    @Override
    public Object getItem(int i) {
        return null;
    }

    @Override
    public long getItemId(int i) {
        return 0;
    }

    @Override
    public int getItemViewType(int position) {
        if (position < (mContactsExceptionNumber.size())) {
            a = 0;
            if(position == (mContactsExceptionNumber.size()-1)){
                ifUse = 1;
            }
            return a;
        } else {
            a = 1;
            return a;
        }
    }

    @Override
    public int getViewTypeCount() {
        return 2;
    }

    @Override
    public void notifyDataSetChanged() {
        super.notifyDataSetChanged();
    }

    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
        final int pos;
        if(mContactsExceptionNumber.size()>0) {
            pos = i - (mContactsExceptionNumber.size());
        }else{
            pos = 0;
        }
        int pos2 = 0;
        int type = getItemViewType(i);
        if(ifUse == 1){
            if(mContactsExceptionNumber.size()>0) {
                pos2 = i - (mContactsExceptionNumber.size());
                Exceptions.index = pos2;
            }
        }
        View v = view;
        if (view == null) {
            switch (type) {
                case 0:
                    v = mInflater.inflate(R.layout.contacts_exception_row, null);
                    name = (TextView) v.findViewById(R.id.contact_name);
                    name.setText(mContactsExceptionNames.get(i));
                    break;
                case 1:
                    v = mInflater.inflate(R.layout.manual_exception_row, null);
                    mManualExceptions = (TextView) v.findViewById(R.id.manual_exception_number);
                    mSwitch = (SwitchCompat) v.findViewById(R.id.manual_exception_switch);
                    mManualExceptions.setText(mPermanentManualException.get(pos2));
                    mSwitch.setTag(i);
                    try {
                        if (mManualExceptionList.contains(mPermanentManualException.get(pos2))) {
                            mSwitch.setChecked(true);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    break;
            }
        }else{
            switch (type) {
                case 0:
                    v = mInflater.inflate(R.layout.contacts_exception_row, null);
                    name = (TextView) v.findViewById(R.id.contact_name);
                    name.setText(mContactsExceptionNames.get(i));
                    break;
                case 1:
                    v = mInflater.inflate(R.layout.manual_exception_row, null);
                    mManualExceptions = (TextView) v.findViewById(R.id.manual_exception_number);
                    mSwitch = (SwitchCompat) v.findViewById(R.id.manual_exception_switch);
                    mManualExceptions.setText(mPermanentManualException.get(pos2));
                    mSwitch.setTag(i);
                    try {
                        if (mManualExceptionList.contains(mPermanentManualException.get(pos2))) {
                            mSwitch.setChecked(true);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    break;
            }
        }

        try {
            mSwitch.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View view, MotionEvent motionEvent) {
                    isTouched = true;
                    return false;
                }
            });
        } catch (NullPointerException e) {
            e.printStackTrace();
        }
        try {
            mSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
                    if (isTouched) {
                        if (b) {
                            if (!mManualExceptionList.contains((mPermanentManualException.get(pos)))) {
                                mManualExceptionList.add((mPermanentManualException.get(pos)));
                            }
                            mSharedPreferences.edit().putString("ManualExceptions", TextUtils.
                                    join(",", mManualExceptionList)).apply();

                        } else {
                            try {
                                mManualExceptionList.remove((mPermanentManualException.get(pos)));
                                mSharedPreferences.edit().putString("ManualExceptions", TextUtils.
                                        join(",", mManualExceptionList)).apply();
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                        Log.d("RejectCall", "Permanent " + TextUtils.join(",", mPermanentManualException));
                        Log.d("RejectCall", TextUtils.join(",", mManualExceptionList));
                    }
                }
            });
        } catch (NullPointerException e) {
            e.printStackTrace();
        }

        return v;
    }

}

      

+3


source to share


1 answer


There are several implementation issues Adapter

. Too much for me to give you advice on how to fix this. I'll just explain how you can effectively implement Adapter

and then you can apply it to yours Adapter

.

Suffice it to say that you should optimally switch to using the new one RecyclerView

, which has many important improvements over the old one ListView

. You can find the documentation RecyclerView

here and the Googles guide on how to use it here .


If you want to display data in ListView

, you must first create a layout for each of the elements in ListView

. For this example, I will use this layout:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="12dp">

    <CheckBox
        android:id="@+id/checkbox"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</RelativeLayout>

      

To bind the data that we want to display in each element ListView

, we write a new class that simply contains the data in the private fields, and getters and setters to get and set that data. Such classes are commonly referred to as view models. The view model for a layout like the one above might look something like this:

public class ExampleViewModel {

    // This is the text which will be set to the CheckBox
    private String text;

    // This boolean will be used to save the checked state of the CheckBox
    private boolean checked;

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public boolean isChecked() {
        return checked;
    }

    public void setChecked(boolean checked) {
        this.checked = checked;
    }
}

      

Each instance of such a view model will represent one item in the ListView

. When one of the elements enters the visible area ListView

, it should be anchored to View

in ListView

(this is what we should implement in getView()

Adapter

). As long as the element is visible, the model will remain bound to it View

, but as soon as it View

exits the visible area ListView

, it will be refactored and attached to another view model, which is simply entering the visible area. This is called image recycling and it is done to minimize memory ListView

and increase overall scroll performance and flow. Views

are very expensive objects, especially inflation Views

andfindViewById()

, the cost is great performance, and the main point is that you only have to inflate a small amount Views

, which can then be reused and therefore you avoid expensive bloating findViewById()

later.

Most of what I explained above happens automatically. What you, as a developer, should do is inflate the correct one Views

in, getView()

or reuse them if there is already one available, then bind the correct view model to View

. I know that most of this seems rather complicated and confusing when you first learn about it, but it becomes much easier and clearer when we start looking at the code.

So now we have the layout of the view items in ListView

and the view model to go along with. Now we need to write another class, commonly referred to as holders. These view holders are essentially container classes around the views in ListView

. Each view item contains one View

associated with the item in ListView

, and they also take care of binding the view model data to View

. Without further ado, here is a view holder to go along with the top view model:

public class ExampleViewHolder {

    // The reference to the CheckBox is saved so we only have to perform the findViewById() once.
    private final CheckBox checkBox;

    // A reference to the view model which is currently bound to this view holder
    private ExampleViewModel currentModel;

    // The View associated with this view holder is passed into the constructor from the Adapter.
    public ExampleViewHolder(View view) {

        // And here we look for all relevant views
        // In our case we just need the CheckBox
        this.checkBox = (CheckBox) view.findViewById(R.id.checkbox);
    }

    public void bind(ExampleViewModel viewModel) {

        // Unset the listener in case there was one from a previous view model
        this.checkBox.setOnCheckedChangeListener(null);

        // Save a reference to the view model which is currently bound to this view holder
        this.currentModel = viewModel;

        // Bind the data to the CheckBox
        this.checkBox.setText(viewModel.getText());
        this.checkBox.setChecked(viewModel.isChecked());

        // Reset the listener
        this.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                currentModel.setChecked(isChecked);
            }
        });
    }
}

      

We're almost done now. The only thing missing right now is to combine it all into Adapter

:

public class ExampleAdapter extends BaseAdapter {

    // Each type of view in the `ListView` gets its own id
    // In this example we only have one type of View so we only need one id
    private static final int EXAMPLE_VIEW_ID = 0;

    // The default view id is just a fallback
    private static final int DEFAULT_VIEW_ID = EXAMPLE_VIEW_ID;

    private final LayoutInflater inflater;
    private List<ExampleViewModel> viewModels;

    public ExampleAdapter(Context context, List<ExampleViewModel> viewModels) {

        // The view models are initially passed in through the constructor.
        // You can pass an empty list into the Adapter if there is not data initially.
        this.viewModels = viewModels;
        this.inflater = LayoutInflater.from(context);
    }

    @Override
    public int getCount() {
        if(viewModels == null) {
            return 0;
        }

        return viewModels.size();
    }

    @Override
    public Object getItem(int position) {
        return viewModels.get(position);
    }

    @Override
    public long getItemId(int position) {

        final Object model = getItem(position);

        // Here we check if the model is an instance of ExampleViewModel and if yes we return its id
        if(model instanceof ExampleViewModel) {
            return EXAMPLE_VIEW_ID;
        }

        return DEFAULT_VIEW_ID;
    }

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

        if(getItemId(position) == EXAMPLE_VIEW_ID) {
            final ExampleViewModel model = (ExampleViewModel) getItem(position);

            final ExampleViewHolder viewHolder;

            // If the convertView is null we need to inflate a new view
            if(convertView == null) {
                final View view = this.inflater.inflate(ExampleViewHolder.LAYOUT, parent, false);   
                viewHolder = new ExampleViewHolder(view);

                // Here we set the viewHolder as tag to the View
                // This is done so we can reuse the same view holder later on
                // Essentially this is the integral part of the whole view recycling process
                view.setTag(viewHolder);
            } else {
                // If the convertView is not null we can just get the view holder with getTag() from the View
                viewHolder = (ExampleViewHolder) convertView.getTag();
            }

            // And we just need to bind the model to the view holder
            viewHolder.bind(model);
        }

        return convertView;
    }
}

      

And that's all you need. This is a pretty practical implementation Adapter

. If you are dealing with two or more different types of views, you need to write a view model and look at the holder class for each type. You can write an interface called ViewModel

it that looks something like this:

public interface ViewModel {

}

      

Every model in your view must implement this interface. Then you can use List<ViewModel>

in Adapter

, which can contain all kinds of kinds of models.

public class TypeOneViewModel implements ViewModel {

}

public class TypeTwoViewModel implements ViewModel {

}

      



Once all the models in your view implement this interface, you can do this:

final List<ViewModel> models = new ArrayList<ViewModel>();
models.add(new TypeOneViewModel());
models.add(new TypeTwoViewModel());
...

      

And this one List

, which now contains several different types of viewmodels, can then be passed to Adapter

. Adapter

will look something like this:

public class ExampleAdapter extends BaseAdapter {

    private static final int TYPE_ONE_VIEW_ID = 0;
    private static final int TYPE_TWO_VIEW_ID = 1;
    private static final int DEFAULT_VIEW_ID = TYPE_ONE_VIEW_ID;

    private final LayoutInflater inflater;
    private List<ViewModel> viewModels;

    public ExampleAdapter(Context context, List<ViewModel> viewModels) {
        this.viewModels = viewModels;
        this.inflater = LayoutInflater.from(context);
    }

    @Override
    public int getCount() {
        if(viewModels == null) {
            return 0;
        }

        return viewModels.size();
    }

    @Override
    public ViewModel getItem(int position) {
        return viewModels.get(position);
    }

    @Override
    public long getItemId(int position) {

        final ViewModel model = getItem(position);

        if(model instanceof TypeOneViewModel) {
            return TYPE_ONE_VIEW_ID;
        }

        if(model instanceof TypeTwoViewModel) {
            return TYPE_TWO_VIEW_ID;
        }

        return DEFAULT_VIEW_ID;
    }

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

        if(getItemId(position) == TYPE_ONE_VIEW_ID) {
            final TypeOneViewModel model = (TypeOneViewModel) getItem(position);

            final TypeOneViewHolder viewHolder;
            if(convertView == null) {
                final View view = this.inflater.inflate(TypeOneViewHolder.LAYOUT, parent, false);
                viewHolder = new TypeOneViewHolder(view);
                view.setTag(viewHolder);
            } else {
                viewHolder = (TypeOneViewHolder) convertView.getTag();
            }

            viewHolder.bind(model);
        }

        if(getItemId(position) == TYPE_TWO_VIEW_ID) {
            final TypeTwoViewModel model = (TypeTwoViewModel) getItem(position);

            final TypeTwoViewHolder viewHolder;
            if(convertView == null) {
                final View view = this.inflater.inflate(TypeTwoViewHolder.LAYOUT, parent, false);
                viewHolder = new TypeTwoViewHolder(view);
                view.setTag(viewHolder);
            } else {
                viewHolder = (TypeTwoViewHolder) convertView.getTag();
            }

            viewHolder.bind(model);
        }

        return convertView;
    }
}

      

You can also combine your owners by writing an abstract class. Such an abstract class would look something like this:

public abstract class ViewHolder<T extends ViewModel> {
    protected final View itemView;

    public ViewHolder(View view) {
        this.itemView = view;
    }

    public abstract void bind(T model);
}

      

If you use this abstract class as the base class for your holders, you should write them like this:

public class TypeOneViewHolder extends ViewHolder<TypeOneViewModel> {
    public TypeOneViewHolder(View view) {
        super(view);

        ...
    }

    public void bind(TypeOneViewModel model) {
        ...
    }
}

      

Although this part is not required. The most important part when working with several different types of elements ListView

is that all models implement a common interface, so you can safely place them in the same one List

.

Anyway, it all looks much simpler and cleaner than yours Adapter

, doesn't it? This way you have a perfect separation between the data in ListView

and Views

that display the data, and it is much more convenient. You can easily implement animation in the view holder without worrying about reworking the view, and many of the requirements are much easier to implement. RecyclerView

takes it all to the next level. It works in much the same way, but has a few significant improvements over it ListView

, I really suggest you take a look at it.


One thing I completely forgot: you can open the internal model of the models List

with a getter so you can modify the view models from the outside. Add methods like this to Adapter

:

public List<ExampleViewModel> viewmodels() {
    return viewModels;
}

public void setViewModels(List<ExampleViewModel> models) {
    viewModels = models;
}

      

Then you can change the view models Adapter

as follows:

adapter.setViewModels(newData);
...
adapter.viewmodels().add(viewModel);

      

And when you're done modifying the data, you can update ListView

by calling notifyDataSetChanged()

on Adapter

.

+8


source







All Articles