Fragment onSaveInstanceState() called after onDestroyView()

584 Views Asked by At

The application started to receive some crashes (it is not reproducible 100%) due to some lifecycle issue for the Fragment.

I'm using view binding and I'm manually invalidating the binding as per Android recommendations to avoid high memory usage in case the reference to the binding is kept after the Fragment is destroyed.

private var _binding: FragmentCustomBinding? = null
private val binding get() = _binding!!

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View = FragmentCustomBinding.inflate(inflater, container, false).also {
    _binding = it
}.root

override fun onDestroyView() {
    _binding = null
    super.onDestroyView()
}

override fun onSaveInstanceState(outState: Bundle) {
    outState.apply {
        putString(BUNDLE_KEY_SOME_VALUE, binding.etSomeValue.text.toString())
    }
    super.onSaveInstanceState(outState)
}

I'm getting a NullPointerException in onSaveInstanceState() as the binding is null as this was called after onDestroyView().

Any idea how I could solve this without manually creating a saved state and manually handling it?

2

There are 2 best solutions below

0
Ionut Negru On BEST ANSWER

It seems the answer for this is in how the fragments are handled, even when they do not have a view, as changes in the Activity state can still trigger onSavedInstanceState() thus I can end up in scenarios where I am in onSavedInstanceState() but without a view. This seems to be intentional as fragments are still supported whether they have a view or not.

The recommendation was to use the view APIs for saving and restoring state (or my SavedStateRegistery).

A few more details can be found here: https://issuetracker.google.com/issues/245355409

1
Narendra_Nath On

The binding = null is causing the issue. To get rid of the _binding = null in the correct manner use this code:

class CustomFragment : Fragment(R.layout.fragment_custom) {

  private val binding: FragmentCustomBinding by viewBinding()
  
  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
      super.onViewCreated(view, savedInstanceState)
      // Any code we used to do in onCreateView can go here instead
  }
}

According to an article on this workaround:

This technique uses an optional backing field and a non-optional val which is only valid between onCreateView and onDestroyView. In onCreateView, the optional backing field is set and in onDestroyView, it is cleared. This fixes the memory leak!