5

I'm dealing here with strange fragment lifecycle behavior.

I have an activity that host two fragments: CityFragment - to display cities list and WeatherFragment - to display the weather forecast for selected city.

When the app starts CityFragment is displayed on the screen, when user selects the city - WeatherFragment is added through supportFragmentManager with backstack. Then, if user want to rotate the screen I'm getting the situation on the pictureenter image description here

  • 0-2 -- CitiesFragment is launched
  • 3-7 -- User select the city and WeatherFragment is displayed on the screen
  • 8-18 -- screen rotation

As you can see in logs right after user selects the city onStop and onDestroyView are called for CitiesFragment, fragment view after this is null. But when screen rotates, CitiesFragment onSaveInstanceState is called (when the view is already destroyed).

The issue here is that after converting the code to kotlin and using synthetic for view access, I'm getting NullPointerException in onSaveInstanceState when I want to save recyclerview first visible element to restore after

val firstVisiblePosition = (recycler_view_cities.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()

with old java implementation, it works fine because I store the reference for recycler_view_cities in the Fragment and I can access it there.

  • Question 1. shouldn't onSaveInstance state for CitiesFragment be called before onStop and onDestroyView?
  • Question 2. how to handle this kind of situation?
Oliver1123
  • 111
  • 1
  • 6

1 Answers1

5

Shouldn't onSaveInstanceState for CitiesFragment be called before onStop and onDestroyView?

Not necessarily. From Fragment docs for onSaveInstanceState():

This corresponds to {@link Activity#onSaveInstanceState(Bundle) Activity.onSaveInstanceState(Bundle)} and most of the discussion there applies here as well. Note however: this method may be called at any time before onDestroy(). There are many situations where a fragment may be mostly torn down (such as when placed on the back stack with no UI showing), but its state will not be saved until its owning activity actually needs to save its state.

onSaveInstanceState() is going to get called by the activity when it is shutting down (except in instances where it is explicitly closed by the user). Your fragment receives a callback, but can be in any state before onDestroy, meaning the UI may have been torn down already.

how to handle this kind of situation?

Check if the UI exists in onSaveInstanceState():

if (fragment.getView() != null) {
   // Your stuff
}
veritas1
  • 8,740
  • 6
  • 27
  • 37
  • I need onSaveInstanceState to save view state (first visible item in recycler_view_cities) but this make no sense if view == null. recycler_view_cities itself is used through kotlin synthetic and the reference is gone along with the view – Oliver1123 May 14 '18 at 12:04
  • @OlegZolotar You cannot save UI state from a view that does not exist. `onDestroyView()` is called on `CitiesFragment` when `WeatherFragment` is added to the back stack. Did you use `replace` or `add` for your fragment transaction? `replace` will cause `onDestroyView()` to be called, maybe that's not what you want? – veritas1 May 14 '18 at 12:39
  • I'm using `replace` for fragment transaction because I want to remove `CitiesFragment` and display `WeatherFragment` for selected city. And I also want to know first visible item in `CitiesFragment` to be able to restore scrolled position when user returns. Any suggestions how to achieve both statements? – Oliver1123 May 14 '18 at 16:58
  • @Oliver1123 If you want to show weather fragment for city, but then return to cities fragment, I would use `add` instead of `replace` on the fragment transaction, and also use `addToBackStack()` on the fragment transaction. I'm not even sure `onSaveInstanceState` is needed for your use case. I have tried having fragments with `add` or `replace`, I've tried recreating the activity....recyclerview maintains correct scroll index :/ – veritas1 May 14 '18 at 18:00