Fragment not being restored from backstack, also Hilt recreating ViewModel with Fragment's lifecycle

Viewed 638

I wanted ViewModel to store bundle of data to save state of Fragment's View since ViewModel is independent of Fragment and its View, but shouldn't save the state as deeply as onSaveInstanceState(outBundle: Bundle) does (save state of EditTexts etc when you navigate throught the app, but no need to handle process death). So I created nullable Bundle property in ViewModel that should do the job (when onDestroyView() is invoked Bundle of data is passed to ViewModel, then in onCreateView(...) I'm reading it, and if it's null then you're in this destination for the first time), but it kept being null.

With the help of Debugging and Logs i figured out that when you navigate throught the app with use of BottomNavigationView and NavigationComponent, Hilt creates new Instance of Fragment, new instance of ViewModel, and then the fragment that should be restored from backstack is being destroyed.

Here are logs that prove it (navigate to another destination and navigate back)

2020-11-24 12:14:31.770 I: Fragment hash code - 160176487: onCreate
2020-11-24 12:14:31.844 I: Fragment hash code - 160176487: VM hash code - 167331582
2020-11-24 12:14:36.264 I: Fragment hash code - 184547386: onCreate
2020-11-24 12:14:36.265 I: Fragment hash code - 184547386: VM hash code - 182438472
2020-11-24 12:14:36.385 I: Fragment hash code - 160176487: onDestroy

It happens only with navigation by BottomNavigationView. When I navigate with findNavController().navigate(...) fragments are restored properly, even adapter position of RecyclerView is being restored.

Shouldn't NavigationComponent restore Fragment from the backstack, and shouldn't ViewModel be independent of Fragment and live throught navigation? And why Hilt creates new instances of ViewModels when they should live throught whole app lifecycle since I didn't scope them to any component or module? Or there is a way to be sure ViewModel is independent?

ViewModel:

class ExploreFragmentViewModel @ViewModelInject constructor(...) : ViewModel()

Fragment:

@AndroidEntryPoint 
class ExploreFragment : Fragment() {
    ...
    private val viewModel: ExploreFragmentViewModel by viewModels()
    ...
}

How i setup BottomNavigationView with NavigationComponent in single Activity pattern:

val navController = findNavController(R.id.fragment)
binding.bottomNavBar.setupWithNavController(navController)
3 Answers

Try to use by activityViewModels()

@AndroidEntryPoint 
class ExploreFragment : Fragment() {
    ...
    private val viewModel: ExploreFragmentViewModel by activityViewModels()
    ...
}

by viewModels create a ViewModel scoped to an individual Fragment. This means each Fragment will have its own individual instance of that ViewModel class.

by activityViewModels() scope a ViewModel to an Activity instead to a Fragment.

private val viewModel by viewModels<MyFragmentViewModel>(
    ownerProducer = { requireActivity() }
)

This works for me and might be feasible for some cases, but I wonder if there's another way to achieve the same behavior as when we use findNavController().navigate(...)

Related