简体   繁体   English

通过LiveData观察器将项目添加到特定索引时出现IndexOutOfBoundsException

[英]IndexOutOfBoundsException when adding items to specific index through LiveData observer

I'm working on a ViewPager fragment that uses FragmentStatePagerAdapter as the adapter. 我正在使用FragmentStatePagerAdapter作为适配器的ViewPager片段。 The Adapter's data is a List object that comes from the database, and I am observing the query with MediatorLiveData. 适配器的数据是一个来自数据库的List对象,我正在用MediatorLiveData观察查询。 MediatorLiveData merges six different lists that I have for one day and turns them into one list that is used by the Adapter. MediatorLiveData合并了我在一天中拥有的六个不同列表,并将它们转换为适配器使用的一个列表。

I want to add an item to the List to a specific index and update the UI dynamically. 我想将列表中的项目添加到特定索引并动态更新UI。 The observer notifies the adapter when an item is added and the update works fine, however when I try to do it on a specific index, calling notifyDataSetChanged() causes an IndexOutOfBoundsError. 当添加项目并且更新工作正常时,观察者会通知适配器,但是当我尝试在特定索引上进行操作时,调用notifyDataSetChanged()会导致IndexOutOfBoundsError。

I initialize the adapter with a list as follows: 我用如下列表初始化适配器:

public MealDetailsPagerAdapter(FragmentManager fm, List<Long> list, long date, MealViewModel vm) {
    super(fm);
    this.mealIdList = list;
    this.date = date;
    this.model = vm;
}

Where MealViewModel is not relevant to this question, it is used on the fragment that the adapter is creating. 如果MealViewModel与该问题无关,则在适配器正在创建的片段上使用它。 The list changes are done by another viewmodel. 列表更改由另一个视图模型完成。

Here's a code that works correctly: 这是一个可以正常工作的代码:

public void changeItems(List<Long> list, long date) {
    if(this.date != date){
        this.mealIdList = list;
        notifyDataSetChanged();
        return;
    }
    mealIdList.addAll(list);
    mealIdList = removeDuplicates(mealIdList);
    System.out.println(Arrays.toString(mealIdList.toArray()));
    notifyDataSetChanged();
}

And the observer that calls it: 观察者称之为:

 @Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    mViewModel.getMealIdsInDay().observe(getViewLifecycleOwner(), longs -> {
        myAdapter.changeItems(longs, mCurrentIndexDay);
        if(isResumed){
            mViewPager.setCurrentItem(myAdapter.getPageIndexForMealId(mViewModel.getMealId()));
            isResumed=false;
        }
        updateIndicator(mViewPager);
    });
}

Where isResumed is false by default, however if the user adds a new Meal isResumed is changed to true and the viewPager's current position gets changed to the created Meal's position. 其中isResumed默认为false,但是,如果用户添加了新的Meal,则isResumed更改为true,并且viewPager的当前位置将更改为创建的Meal的位置。

However, with the working code, the created Meal's position will always be at the end of the adapter's List because of addAll(). 但是,使用工作代码,由于addAll(),创建的Meal的位置将始终位于适配器的List的末尾。 I want to add the meal to a specific position, but if I get the index with mViewPager.getCurrentItem() and send it to the method as follows: 我想将餐点添加到特定位置,但是如果我使用mViewPager.getCurrentItem()获得索引,则将其发送给方法如下:

    mealIdList.addAll(index, list);

the addAll itself works, but notifyDataSetChanged() causes an IndexOutOfBoundsError. addAll本身可以工作,但是notifyDataSetChanged()会导致IndexOutOfBoundsError。

Here's the complete stack trace: 这是完整的堆栈跟踪:

    E/AndroidRuntime: FATAL EXCEPTION: main
Process: fi.seehowyoueat.shye.debug, PID: 14148
java.lang.IndexOutOfBoundsException: Index: 2, Size: 2
    at java.util.ArrayList.set(ArrayList.java:453)
    at androidx.fragment.app.FragmentStatePagerAdapter.destroyItem(FragmentStatePagerAdapter.java:147)
    at androidx.viewpager.widget.ViewPager.populate(ViewPager.java:1212)
    at androidx.viewpager.widget.ViewPager.setCurrentItemInternal(ViewPager.java:669)
    at androidx.viewpager.widget.ViewPager.setCurrentItemInternal(ViewPager.java:631)
    at androidx.viewpager.widget.ViewPager.dataSetChanged(ViewPager.java:1086)
    at androidx.viewpager.widget.ViewPager$PagerObserver.onChanged(ViewPager.java:3097)
    at androidx.viewpager.widget.PagerAdapter.notifyDataSetChanged(PagerAdapter.java:291)
    at fi.octo3.shye.view.viewpagers.MealDetailsPagerAdapter.changeTheItems(MealDetailsPagerAdapter.java:87)
    at fi.octo3.shye.fragments.MealDetailsFragment.lambda$onActivityCreated$0(MealDetailsFragment.java:223)
    at fi.octo3.shye.fragments.-$$Lambda$MealDetailsFragment$XB4Svnx84FE6kVa5Gzle01e8F3o.onChanged(Unknown Source:4)
    at androidx.lifecycle.LiveData.considerNotify(LiveData.java:131)
    at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:149)
    at androidx.lifecycle.LiveData.setValue(LiveData.java:307)
    at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
    at fi.octo3.shye.models.viewmodel.-$$Lambda$2CouvY7DQv4NA0nk6EMoH6jUavw.onChanged(Unknown Source:4)
    at androidx.lifecycle.MediatorLiveData$Source.onChanged(MediatorLiveData.java:152)
    at androidx.lifecycle.LiveData.considerNotify(LiveData.java:131)
    at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:149)
    at androidx.lifecycle.LiveData.setValue(LiveData.java:307)
    at androidx.lifecycle.LiveData$1.run(LiveData.java:91)
    at android.os.Handler.handleCallback(Handler.java:873)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:280)
    at android.app.ActivityThread.main(ActivityThread.java:6748)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

Looking at it, it seems that the problem is somehow caused by the LiveData, but I'm not sure how to fix it. 看起来,问题似乎是由LiveData引起的,但是我不确定如何解决它。 If you want to look at the method that updates the MediatorLiveData that I have it's here: 如果要查看更新MediatorLiveData的方法,请点击此处:

 private void refresh(long key){
    if(groupsAndMealsMap.get(key) != null){
        //remove source and then value from map
        liveDataMerger.removeSource(groupsAndMealsMap.get(key));
        groupsAndMealsMap.remove(key);
        //add new value to map and add it as a source
        groupsAndMealsMap.append(key, mealIdsInGroup);
        liveDataMerger.addSource(groupsAndMealsMap.get(key), liveDataMerger::setValue);
    }
}

refresh() is called by addItem() that gets an updated List of Meals from the database (list being mealIdsInGroup) and liveDataMerger consists of six LiveData> objects. update()由addItem()调用,它从数据库中获取更新的餐单(列表为进餐ID),并且liveDataMerger由六个LiveData>对象组成。

Any help will be appreciated. 任何帮助将不胜感激。 Thank you! 谢谢!

EDIT 编辑

Here's the addItem() method, as you can see the service executor waits for the operation to be done before moving on to the refresh() method. 这是addItem()方法,您可以看到服务执行程序在继续执行刷新()方法之前等待操作完成。

public void addItem(Meal meal, long mealGroupId){
    ExecutorService service = Executors.newCachedThreadPool();
    service.execute(() -> {
        setMealId(db.mealDao().insertMealIntoGroup(meal, mealGroupId));
        mealIdsInGroup = db.mealDao().loadMealsWithinGroup(mealGroupId);
    });
    service.shutdown();
    try {
        service.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    refresh(mealGroupId);
}

I suspect that the issue occurs because the insert operation is not over yet. 我怀疑发生此问题是因为插入操作尚未结束。

Use a Handler to put a delay 使用Handler来延迟

       new Handler(getMainLooper()).postDelayed(new Runnable() {
            @Override
            public void run() {
                notifyDataSetChanged();
            }
        }, 500);

500 ms just for testing. 仅用于测试需要500毫秒。

Does that issue occurs no matter of what position you're in? 无论您处于哪个位置,都会发生该问题吗?

Because if you are in position 0 of the adapter, the adapter didn't created the fragment that's in 3rd position yet. 因为如果您位于适配器的位置0,则适配器尚未创建位于第三位置的片段。

Remember that the one of the main differences between ViewPagerAdapter and FragmentStatePagerAdapter is that the latter only creates 3 fragments at a time, and if you are in the first position or in the last, the adapter will only contain 2 instances of fragments. 请记住,ViewPagerAdapter和FragmentStatePagerAdapter之间的主要区别之一是后者一次仅创建3个片段,并且如果您位于第一个位置或最后一个位置,则适配器将仅包含2个片段实例。

Let me know if this solution helped you! 让我知道此解决方案是否对您有帮助!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM