Search code examples
javaandroidarraylistandroid-fragmentactivity

Delete item from arraylist on Fragment from Activity


I’m trying to learn about passing data back and forth between Fragments with Activities right now by creating a simple grocery list where items can be deleted. The grocery ArrayList will live within the Activity along with a delete button, and the Fragment will display the ArrayList. When the app launches and everything loads, the user will be able to select an item from the list in Fragment. When user hits Delete, the selected item in the ArrayList will be deleted and the Fragment will reload the updated list.

I can launch the app and everything loads correctly until I delete an item. The item deletes (I have a log to check) but I need to find a way to update the ArrayList in the fragment. When I try to delete another item again, the app crashes.

Here's my logcat:

java.lang.IllegalStateException: The content of the adapter has 
changed but ListView did not receive a notification. Make sure 
the content of your adapter is not modified from a background 
thread, but only from the UI thread. Make sure your adapter calls 
notifyDataSetChanged() when its content changes. [in ListView(2131427424,
class android.widget.ListView) with Adapter(class android.widget.ArrayAdapter)]

Here's my MainActivity:

public class MainActivity extends AppCompatActivity implements Tab1.OnDataPass {

public SectionsPageAdapater mSectionsPageAdapter;
ViewPager mViewPager;

ArrayList<String> valuesArray = new ArrayList<String>();

int index = -1;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mSectionsPageAdapter = new SectionsPageAdapater(getSupportFragmentManager());
    mViewPager = (ViewPager) findViewById(R.id.viewContainer);

    valuesArray.add("apple");
    valuesArray.add("banana");
    valuesArray.add("cherry");
    valuesArray.add("dragon fruit");
    valuesArray.add("eggplant");

    mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        }

        @Override
        public void onPageSelected(int position) {

        }

        @Override
        public void onPageScrollStateChanged(int state) {

        }
    });

    setupViewPager(mViewPager);
}

@Override
public void onDataPass(Integer indexNum) {
    index = indexNum;
}

public void onClickDelete (View v) {

    if (index >= 0) {
        valuesArray.remove(index);
    }

    index = -1;
}

private void setupViewPager(ViewPager viewPager) {
    SectionsPageAdapater adapter = new SectionsPageAdapater(getSupportFragmentManager());
    adapter.addFragment(new Tab1(), "TAB1");
    viewPager.setAdapter(adapter);
}

public ArrayList<String> getDataLog() {
    return valuesArray;
}
}

My Fragment:

public class Tab1 extends Fragment {
Button btnPass, btnPass2;
ListView listview;
ArrayList<String> valuesArray = new ArrayList<String>();

int index = -1;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.activity_tab1,container,false);

    btnPass = view.findViewById(R.id.btnPass);
    btnPass2 = view.findViewById(R.id.btnPass2);

    listview = view.findViewById (R.id.listview);

    MainActivity activity = (MainActivity) getActivity();
    valuesArray = activity.getDataLog();


    ArrayAdapter<String> adapter = new ArrayAdapter<String>(getContext(),
            android.R.layout.simple_list_item_1, valuesArray);
    listview.setAdapter(adapter);

    listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> adapter, View view, int position, long id) {
            index = (int) id;
            passData((index));
        }
    });

    return view;
}



OnDataPass dataPasser;

public interface OnDataPass {
    void onDataPass(Integer index);
}

@Override
public void onAttach(Context a) {
    super.onAttach(a);
    dataPasser = (OnDataPass) a;
}

public void passData(Integer index) {
    dataPasser.onDataPass(index);
}
}

And my adapter:

public class SectionsPageAdapater extends FragmentPagerAdapter {

private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();

public void addFragment(Fragment fragment, String title) {
    mFragmentList.add(fragment);
    mFragmentTitleList.add(title);
}

public SectionsPageAdapater(FragmentManager fm) {
    super(fm);
}

@Override
public CharSequence getPageTitle(int position) {
    return mFragmentTitleList.get(position);
}

@Override
public Fragment getItem(int position) {
    return mFragmentList.get(position);
}

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

Solution

  • In Android, every ListView object has a corresponding Adapter object that actually holds the data and passes the View to the ListView to show it. The caveat is that every time the data, the adapter is holding changes, you have to explicity call notifyDatasetChanged method of your adapter to ensure that your ListView object is notified about the changes and handles the corresponding UI updates.

    Hence, in your case, you should store your ArrayAdapter object in your Activity class, and pass it to your Fragment instead of the actual ArrayList that holds the data. Whenever a user presses the delete button, onClickDelete method will be called. Inside that method, after you mutate the actual ArrayList, you should call your adapter's notifyDatasetChanged method.