PagerAdapter changed adapters content with notifyi

2019-09-18 19:52发布

So first things first, here's the error that I'm getting:

java.lang.IllegalStateException: The application's PagerAdapter changed the adapter's contents without calling PagerAdapter#notifyDataSetChanged! Expected adapter item count: 18, found: 28

I'm using a RecyclerView in my main activity and that has a List<Objects> as the dataset, that's all fine.

From that activity I call the second activity when a RecyclerView item is clicked that is basically a gallery implemented using a ViewPager using this code:

public void startSlideActivity(final int position) {
  DataTransferer.get().storeItems(feed);

  Intent i = new Intent(context, SlideActivity.class);
  ...
  i.putExtra("POSITION", position);
  context.startActivity(i);
}

My data is too large to transfer through an intent (using Parcelable or otherwise) so I'm using a singleton to hold and transfer my list, here's the code:

public class DataTransferer {

  private static volatile DataTransferer singleton;

  private List<Thing> items;

  public static DataTransferer get(){
    if (singleton == null) {
      synchronized (DataTransferer.class) {
        singleton = new DataTransferer();
      }
    }
    return singleton;
  }

  private DataTransferer(){
  }

  public void storeItems(List<Thing> items){
    this.items = items;
  }

  public List<Thing> getStoredItems(){
    return items;
  }
}

In the second activity I set the adapter and retrieve the list like so:

feed = DataTransferer.get().getStoredItems();
final int position = this.getIntent().getIntExtra("POSITION", 0);
adapter = new FeedPagerAdapter(SlideActivity.this, feed);
viewPager.setAdapter(adapter);
adapter.notifyDataSetChanged();
viewPager.setCurrentItem(position);
viewPager.setOffscreenPageLimit(4);

And finally here's in my PagerAdapter code:

public class FeedPagerAdapter extends PagerAdapter {
  @BindView(R.id.item_image_view) ImageView image;

  private final SlideActivity host;
  private List<Thing> items;

  public FeedPagerAdapter(SlideActivity host, List<Thing> items){
    this.host = host;
    this.items = items;
    notifyDataSetChanged();
  }

  @Override
  public Object instantiateItem(ViewGroup parent, int position) {
    View view = LayoutInflater.from(host).inflate(R.layout.item, parent, false);
    ButterKnife.bind(this, view);
    ...
    parent.addView(view);
    return view;
  }

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

  @Override
  public void destroyItem(ViewGroup container, int position, Object object) {
    container.removeView((View)object);
  }

  @Override
  public boolean isViewFromObject(View view, Object object) {
    return view == object;
  }
}

I've tried notifying the dataset in onResume and onPause and getItemCount in the adapter also, same problem.

Back to the main activity, this data is loaded over the network and adds items to the list when the load finishes. If I start my application and click on the item as soon they start to populate the RecyclerView, it opens the second activity and I get the crash. If I start the app and wait a second and click a RecyclerView item, it works as intended.

If anyone has any suggestions on how I can wait to make sure the list is stable when starting the second activity or a better way to implement a grid based RecyclerView gallery to open a viewpager type layout with the same dataset I would really appreciate it.

1条回答
聊天终结者
2楼-- · 2019-09-18 20:43

It's because you are using this line viewPager.setOffscreenPageLimit(4);, It's mean viewpager won't re-create the screen in all 4 pages. However, there is a function to detect if your screen has been visible completely, it's call setUserVisibleHint(). You just need to use like below: //For the Fragment case

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        if (isVisibleToUser) {
         //TODO notify your recyclerview data over here
        }
    }

EDIT:

For the Activity case: If targeting API level 14 or above, one can use

android.app.Application.ActivityLifecycleCallbacks

public class MyApplication extends Application implements ActivityLifecycleCallbacks {
    private static boolean isInterestingActivityVisible;

    @Override
    public void onCreate() {
        super.onCreate();

        // Register to be notified of activity state changes
        registerActivityLifecycleCallbacks(this);
        ....
    }

    public boolean isInterestingActivityVisible() {
        return isInterestingActivityVisible;
    }

    @Override
    public void onActivityResumed(Activity activity) {
        if (activity instanceof MyInterestingActivity) {
             isInterestingActivityVisible = true;
        }
    }

    @Override
    public void onActivityStopped(Activity activity) {
        if (activity instanceof MyInterestingActivity) {
             isInterestingActivityVisible = false;
        }
    }

    // Other state change callback stubs
    ....
}

Register your application class in AndroidManifest.xml:

  <application
        android:name="your.app.package.MyApplication"
        android:icon="@drawable/icon"
        android:label="@string/app_name" >

Add onPause and onResume to every Activity in the project:

@Override
protected void onResume() {
  super.onResume();
  MyApplication.activityResumed();
}

@Override
protected void onPause() {
  super.onPause();
  MyApplication.activityPaused();
}

In your finish() method, you want to use isActivityVisible() to check if the activity is visible or not. There you can also check if the user has selected an option or not. Continue when both conditions are met.

查看更多
登录 后发表回答