1

I have an app which is having Master/Detail kind of architecture. When I select some item, the details are displayed in details fragment.

For the detail fragment to know what to load id is sent over via Arguments. Using SavedStateHandle in VM I can directly read these arguments from the handle without re-routing it in Fragment itself. That works great for the first detail fragment. The problem comes with next selections.

Every time I load details, the id from first selection is populated although every time a new fragment is created along with new ViewModel.

I was looking into the code of lifecycle-viewmodel-savedstate library (v.2.3.1) and found this method in SavedStateHandle called:

static SavedStateHandle createHandle(@Nullable Bundle restoredState,
        @Nullable Bundle defaultState) {
    if (restoredState == null && defaultState == null) {
        return new SavedStateHandle();
    }

    Map<String, Object> state = new HashMap<>();
    if (defaultState != null) {
        for (String key : defaultState.keySet()) {
            state.put(key, defaultState.get(key));
        }
    }

    if (restoredState == null) {
        return new SavedStateHandle(state);
    }

    ArrayList keys = restoredState.getParcelableArrayList(KEYS);
    ArrayList values = restoredState.getParcelableArrayList(VALUES);
    if (keys == null || values == null || keys.size() != values.size()) {
        throw new IllegalStateException("Invalid bundle passed as restored state");
    }
    for (int i = 0; i < keys.size(); i++) {
        state.put((String) keys.get(i), values.get(i));
    }
    return new SavedStateHandle(state);
}

Here I can see that in defaultState the correct id is set for every new view model. But as you can see defaultState is processed first and restoredState is processed after that. restoredState contains same key with old id which at the end replace the correct one from defaultState.

I can understand that is probably wanted behavior for real restoring but in my case I'm not restoring the fragment. Yes, the class is same but I'm just replacing detail fragment with another detail fragment with new/different data.

Am I doing something wrong? Can I give the framework a hint that this is not restoration and I'm not interested in saved values from old fragment?

bio007
  • 893
  • 11
  • 20
  • If you are creatung a brand new fragment and therefore getting a new ViewModel, then `restoredState` would be null. What makes you think it isn't null? – ianhanniballake Apr 16 '21 at 21:33
  • Debugging? I can see it's not null. Also saveInstanceState should by definition save state between instances so I'm not surprised. But in this case it's not really desired behavior. – bio007 Apr 16 '21 at 22:11
  • A brand new Fragment and a ViewModel associated work a brand new Fragment has no saved state. Please include how you create your brand new Fragment and how you create your ViewModel – ianhanniballake Apr 16 '21 at 22:38
  • Ok found out what was causing it! Thanks for pointing out to Fragment creation, looking into it deeper I found the culprit. See the answer. – bio007 Apr 18 '21 at 16:27

1 Answers1

0

What I didn't mentioned was that I'm using ViewPager2 for Master/Detail view. I didn't consider it having some influence on state staving but it has.

ViewPager2 library is saving and restoring state on the background which is then used by SavedStateHandle. To map fragments with states it uses ids from FragmentStateAdapter. I didn't override getItemId method so ids were assigned by position.

This of course map same state to new fragments on the exact position. Implementing getItemId which assign different ids to different instances of fragment fixed the problem.

bio007
  • 893
  • 11
  • 20