简体   繁体   中英

Restoring an Adapter on Fragment Recreation

I'm running into a crash where if the android OS kills a process, when the application attempts to reconstruct the activity and fragment from a saved instance, my recyclerview's adapter is always null.

Here is my hierarchy:

Parent Activity    // destroyed when process is killed
  Nested View Pager Fragment // view pager containing four recycler view fragments
    Recycler Frag 1
    Recycler Frag 2
    Recycler Frag 3
    Recycler Frag 4

Each one of these recycler fragments is the exact same fragment except, when the parent activity creates a new instance of them, I call an exposed setter method that sets an adapter of type RecyclerView.Adapter (reason being that each one of these frags may be a paginating recycler but the content it displays and how the user interacts with it may vary) example code from the parent activity:

RecyclerFrag feedFrag = RecyclerFrag.newInstance();
feedFrag.setAdapter(new myCustomAdapter());

Now, my end users are running into a crash where if they have this activity backgrounded, and the OS decides to kill the process that this activity lives on, it does not restore itself properly. From what I can see, everything is there on restore except the adapter which is null. The code then tries to access the adapters interface and crashes with a NPE.

I can not figure out why this is happening. I am also trying to wrap my head around restoring activities and fragments in events like these. I've read a bunch of posts and documentation about state restoring and still can not find a solution. Here is an example of solutions I have tried and maybe someone can point out the flaw / gap in my logic.

Have the recycler frag Store itself in the fragmentManager and attempt to read it back in the onActivityCreated Method call.

@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);
    getFragmentManager().putFragment(outState, "myFrag", this);
    ...
}

then:

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if(savedInstanceState != null) {
        getFragmentManager().getFragment(savedInstanceState, "myFrag");
        ...
    }
}

To me, This feels like I am missing the mark. It would make no sense for a fragment to grab a reference to itself and then try and set itself from it. Even worse, the fragment it returns has a null adapter. Live and learn.

Next I tried saving the fragments I create in the parent activity for the viewPager implementation, then try and restore these fragments in the parents onCreate. The parent class would look like the following:

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    getSupportFragmentManager().putFragment(outState, "frag1", frag1);
    ...
}

then:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if(savedInstanceState != null) { 
        getSupportFragmentManager.getFragment(savedInstanceState, "myFrag);
        ...
    }
}

What is interesting to note about this approach is when I save the fragment to the manager in the onSaveInstance method, my adpater is not null which makes sense. When I grab it in the onCreate method, that fragments adapter is now null.

This leaves me with my final thought that maybe restoring an adapter is not efficient and shouldn't be done this way. Yet again, it doesn't make sense for me to create the adapter inside the Recycler Fragment since I want this fragment to take any adapter implementation of type RecyclerView.Adapter . For further context, the viewPager implementation is of type FragmentStatePagerAdapter which supplies the bare minimum method overrides the get it to run. Which to my understanding, is exactly what you should do and the parent FragmentStatePagerAdapter will handle the rest of the restoring process for you. Am I wrong in this assumption?

I'd greatly appreciate any input on this matter. I tried to provide as much information as I possibly could however, if you feel that you might be able to assist me given more information, I would be happy to update my post if you just let me know. Thanks!

Final Edit: I ended up going with a similar solution to the one marked as the answer to this post. I will provide my actual solution below so if anyone in the future finds this post helpful to their situation.

  1. Remove the public setter method in the Recycler Fragments interface. Given I was always calling a setter right after initializing the fragment, it made sense that something was wrong with that approach and is prone to error when future developers have to work in the code.
  2. Create an enumerated type as a data abstraction for the objects in the recycler. Given that certain implied types of data needed different adapters, this seems to be appropriate as well. What I mean by this is I might have a car object but depending on where I am at in a user journey that car object might need a different adapter reflected with enums like CAR_PREVIEW_SEARCH or CAR_PREVIEW_SELECTABLE which comes into play during the onCreateView

I can't confidently solve your actual issue without seeing more of the code base, however the below line was a little concerning;

Each one of these recycler fragments is the exact same fragment except, when the parent activity creates a new instance of them, I call an exposed setter method that sets an adapter of type RecyclerView.Adapter

Can I suggest an alternative way of setting a type?

In your FragmentStatePagerAdapter create an enum of your various fragment types;

public enum Page {
    PAGE_ONE,
    PAGE_TWO,
    PAGE_THREE
}

Then in your getItem(int position) implementation you would have;

@Override
public Fragment getItem(int position) {
    CustomFragment fragment = CustomFragment();

    Bundle bundle = new Bundle();
    bundle.putInt(CustomFragment.INDEX, position); // You could swap this to whatever serialised information you need

    fragment.setArguments(bundle);

    return fragment;
}

In the onCreateView implementation of your CustomFragment you can access this bundle like this;

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

    RecyclerView.Adapter adapter;

    if(getArguments() != null) {
        switch (FragmentStatePagerAdapter.Page.values()[getArguments().getInt(CustomFragment.INDEX)]) {
            // Assign whatever adapter/values ect in here
        }
    } else {
        // setup various default states here
        adapter = SomeDefaultCustomFragmentAdapter()
    }

    // set adapters etc

    return view;
}

The above ensures that you're only manipulating the adapter when you definitely have the view. When the system recreates the fragments everything should work as normal.

Hopefully this is at least somewhat helpful.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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