Issue
My app allows users to submit a form (an Androidx Fragment) to save data. After the form is submitted ~30 times, the app runs out of memory and crashes. A comparison of memory dumps shows that one of the sources of the problem is that the ViewModel associated with the form's fragment is not destroyed when the form is submitted.
The ViewModel is currently scoped to the fragment using the 'this' keyword in the Fragment's onViewCreated()
method:
vm = new ViewModelProvider(this).get(AddInventoryVM.class);
When the user submits the form, the fragment is reloaded by using Android Navigation from Android Architecture Components to navigate to the same fragment.
navController.navigate(R.id.addInventoryFragment, null);
During this transition, the instance of the ViewModel is not garbage collected. onViewCreated()
gets called and a new instance of the ViewModel is created - leading to memory issues when this happens multiple times. 'onDestroy()
' is not called, but onDestroyView()
is called during the transition.
The reason might be that the Fragment instance is not destroyed during the transitions (leading to the ViewModel not being garbage collected) - only the Fragment's view is destroyed. However, if this were the case, something doesn't add up - wouldn't Android reuse the existing ViewModel on transition instead of creating a new one?
The discrepancy above notwithstanding, would scoping the ViewModel to the Fragment's ViewLifecycleOwner()
be a good way to resolve the issue?
So changing this:
vm = new ViewModelProvider(this).get(AddInventoryVM.class);
to this:
vm = new ViewModelProvider(getViewLifecycleOwner()).get(AddInventoryVM.class);
Solution
As per the Navigate to a destination documentation:
Android maintains a back stack that contains the destinations you've visited. The first destination of your app is placed on the stack when the user opens the app. Each call to the
navigate()
method puts another destination on top of the stack.
So when you call navController.navigate(R.id.addInventoryFragment, null);
, you're adding a brand new instance of your add inventory fragment to the back stack, meaning your back stack now has two copies. if you call navigate()
again, you'll have three copies, then four, etc.
You can either use popUpTo
or, if you know you're always replacing the Fragment with itself, use setLaunchSingleTop(true)
:
NavOptions options = new NavOptions.Builder()
.setLaunchSingleTop(true)
.build();
navController.navigate(R.id.addInventoryFragment, null, options);
(If you're using an <acction>
in Navigation XML, you'd use app:launchSingleTop="true"
do to the same thing)
Answered By - ianhanniballake
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.