Coova
Coova

Reputation: 1858

IndexOutOfBoundsException issue with ArrayAdapter Class

I am trying to figure out why am I receiving the following error.

java.lang.IndexOutOfBoundsException: Invalid index 2, size is 2
        at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)
        at java.util.ArrayList.get(ArrayList.java:308)
        at com.modup.adapter.WorkoutCardsAdapter.getItem(WorkoutCardsAdapter.java:75)
        at com.modup.fragment.CreateFragment$1.onPressed(CreateFragment.java:143)
        at com.modup.adapter.WorkoutCardsAdapter$1.onClick(WorkoutCardsAdapter.java:62)
        at android.view.View.performClick(View.java:4442)
        at android.view.View$PerformClick.run(View.java:18473)
        at android.os.Handler.handleCallback(Handler.java:733)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:136)
        at android.app.ActivityThread.main(ActivityThread.java:5105)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:515)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:792)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:608)
        at dalvik.system.NativeStart.main(Native Method)

I have seen many posts on this topic, but they all seem to have the same answer, and after implementing those answers it seems as if the issue still persists.

My Case:

I have a class that extends ArrayAdapter<>, and this Adapter class is used to populate a ListView inside my fragment. Everything works fine, I can add views, and I can remove views (sort of), I can access the callback implemented.

The Problem: It seems as if that when I add many views 3-4+ and start to remove them that I will eventually hit this exception.

Specific Cases: If I start from the top view list item, and go down I don't seem to throw an error, and also I can sometimes go from the bottom up. The error seems to come in if I remove the views out of order (sometimes).

How can I fix this issue? Am I Overriding getCount() and getItem() incorrectly?

UPDATE:

When checking the position of the mCallback.onPress(position); it appears as if the position of the first item in the list is 0, and will remain 0 as long as I don't try to remove any list item below it. Once I remove a list item below it, the first list item will have a completely different position.

public class WorkoutCardsAdapter extends ArrayAdapter {

private LayoutInflater mInflater;
private String[] repArray, setArray, mgArray;
String TAG = WorkoutCardsAdapter.class.getCanonicalName();
Callback mCallback;
List<WorkoutView> mViews;

public WorkoutCardsAdapter(Context context, List<WorkoutView> views, Callback callback) {
    super(context, 0, views);
    mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    mCallback = callback;
    this.mViews = views;
}

@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    final ViewHolder holder;
    if (convertView == null) {
        convertView = mInflater.inflate(R.layout.single_workout_layout, parent, false);
        holder = new ViewHolder();
        holder.spinnerReps = (Spinner) convertView.findViewById(R.id.spinnerReps);
        holder.spinnerSets = (Spinner) convertView.findViewById(R.id.spinnerSets);
        holder.spinnerMuscleGroup = (Spinner) convertView.findViewById(R.id.spinnerWorkoutGroup);
        holder.etWorkoutName = (EditText) convertView.findViewById(R.id.etWorkoutName);
        holder.btnRemoveWorkout = (Button) convertView.findViewById(R.id.btnRemoveWorkout);
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }
    holder.btnRemoveWorkout.setTag(position);

    mgArray = getContext().getResources().getStringArray(R.array.string_array_muscle_groups);
    TextSpinnerAdapter adapter = new TextSpinnerAdapter(getContext(), R.layout.spinner_text_item, mgArray);
    adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    holder.spinnerMuscleGroup.setAdapter(adapter);

    setArray = getContext().getResources().getStringArray(R.array.string_array_sets);
    TextSpinnerAdapter adapter1 = new TextSpinnerAdapter(getContext(), R.layout.spinner_text_item, setArray);
    adapter1.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    holder.spinnerSets.setAdapter(adapter1);

    repArray = getContext().getResources().getStringArray(R.array.string_array_reps);
    TextSpinnerAdapter adapter2 = new TextSpinnerAdapter(getContext(), R.layout.spinner_text_item, repArray);
    adapter2.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    holder.spinnerReps.setAdapter(adapter2);

    holder.btnRemoveWorkout.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            int position = (Integer) v.getTag();
            mCallback.onPressed(position);
        }
    });

    return convertView;
}

@Override public WorkoutView getItem(final int position) { return mViews.get(position); }

@Override
public int getCount() {
    return mViews.size();
}

public interface Callback {
    void onPressed(int pos);
}

private static class ViewHolder {
    public Spinner spinnerMuscleGroup;
    public Spinner spinnerSets;
    public Spinner spinnerReps;
    public EditText etWorkoutName;
    public Button btnRemoveWorkout;
}

}

Here is the Adapter Implementation w/ Callback

mWorkoutCardsAdapter = new WorkoutCardsAdapter(getActivity(), mArrayList, new WorkoutCardsAdapter.Callback() {
    @Override
    public void onPressed(int pos) {
            mWorkoutCardsAdapter.remove(mWorkoutCardsAdapter.getItem(pos));
            mWorkoutCardsAdapter.notifyDataSetChanged();
    }
});

In case it will help, this is how I add a new view on button press

    workoutView = new WorkoutView(getActivity());
    mWorkoutCardsAdapter.add(workoutView);
    mWorkoutCardsAdapter.notifyDataSetChanged();

Upvotes: 1

Views: 1128

Answers (1)

Mike M.
Mike M.

Reputation: 39191

I believe the problem is that you're not caching the correct position with the corresponding Button, so the items being removed are getting mixed up. You can set the Button's tag to the position, and retrieve it from the View passed into the onClick() method, to ensure the right one is removed from the list.

holder.btnRemoveWorkout.setTag(position);
holder.btnRemoveWorkout.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        int position = (Integer) v.getTag();
        mCallback.onPressed(position);
    }
});

Upvotes: 2

Related Questions