Fixed
Status Update
Comments
be...@google.com <be...@google.com>
[Deleted User] <[Deleted User]> #2
After looking more into this issue, ViewModels are persisted using retained fragments. After adding retained fragments to the sample app, we can observe that retained fragments are not able to survive landscape lock and unlock scenario either.
se...@google.com <se...@google.com> #3
Hey, currently I can't reproduce your issue.
Link you provided doesn't work, in my simple test project I don't observe described issue.
Could you please say reupload your project somewhere else?
Could you please say what kind of lockscreen, do you use? (it may effect issue)
Could you reproduce this behavior on other devices?
Link you provided doesn't work, in my simple test project I don't observe described issue.
Could you please say reupload your project somewhere else?
Could you please say what kind of lockscreen, do you use? (it may effect issue)
Could you reproduce this behavior on other devices?
[Deleted User] <[Deleted User]> #4
Hey,
Sorry about the repo link, here is the public one:https://github.com/karmazindmitriy/viewmodelscoping , git@github.com:karmazindmitriy/viewmodelscoping.git
Tested on:
1. Device: NEXUS 5X, API level: 24, lock method: PIN / PATTERN / SWIPE / PASSWORD. Can reproduce by unlocking with any of the unlocking methods. Can also reproduce by unlocking with fingerprint.
2. Device: NEXUS 5X, API level: 27, lock method: PIN / PATTERN / PASSWORD . Can reproduce only when unlocking the device with a fingerprint. Unlocking with PIN / PATTERN / PASSWORD works fine.
3. Device: Pixel, API level: 27, lock method: PIN (haven't tested other) + fingerprint. Can reproduce only when unlocking the device with a fingerprint. Unlocking with PIN / PATTERN / PASSWORD works fine.
Hope this is helpful, please let me know if you need any other info.
Sorry about the repo link, here is the public one:
Tested on:
1. Device: NEXUS 5X, API level: 24, lock method: PIN / PATTERN / SWIPE / PASSWORD. Can reproduce by unlocking with any of the unlocking methods. Can also reproduce by unlocking with fingerprint.
2. Device: NEXUS 5X, API level: 27, lock method: PIN / PATTERN / PASSWORD . Can reproduce only when unlocking the device with a fingerprint. Unlocking with PIN / PATTERN / PASSWORD works fine.
3. Device: Pixel, API level: 27, lock method: PIN (haven't tested other) + fingerprint. Can reproduce only when unlocking the device with a fingerprint. Unlocking with PIN / PATTERN / PASSWORD works fine.
Hope this is helpful, please let me know if you need any other info.
se...@google.com <se...@google.com> #5
Thanks, yep, I was able to reproduce it.
se...@google.com <se...@google.com> #6
Oh, another instance when platform doesn't call onSaveInstanceState, but calls onStop and onRetainNonConfiguration, so fragments are confused, we are going to fix fragments so they work around this behavior.
[Deleted User] <[Deleted User]> #7
Are you referring to the first `onStop` that happens right after unlock (line 12 in screenshot)?
se...@google.com <se...@google.com> #8
yep
do...@gmail.com <do...@gmail.com> #9
Hello, I Have notice the same issue when using ViewModel, here is the scenario:
1) open ActivityA
-gets ViewModel with reference: TestViewModel@e71fab8
2) rotate device
-gets ViewModel with reference: TestViewModel@e71fab8
3) open ActivityB
4) rotate device
5) press back (finish Activity B)
6) Activity A comes back from stack
-gets ViewModel with reference: TestViewModel@260072e
I have tested with two kinds of ViewModel, with factory and default creation method:
ViewModelProviders.of(this).get(TestViewModel::class.java)
ViewModelProviders.of(this, factory).get(DashboardViewModel::class.java)
Both Activities extends AppCompatActivity class.
I have made some research and find case when this issue do not happend:
1) open ActivityA (vertical)
-gets ViewModel with reference: TestViewModel@e71fab8
2) rotate device (horizontal)
-gets ViewModel with reference: TestViewModel@e71fab8
3) open ActivityB (horizontal)
4) rotate device (vertical)
5) rotate device (horizontal)
6) press back (finish Activity B)
7) Activity A comes back from stack (horizontal)
-gets ViewModel with reference: TestViewModel@e71fab8
So when I rotate device twice on Activity B and press back the Activity A do not recreate ViewModel, mayeby it is becouse it has the same orientation as befor putting it in activities stack.
1) open ActivityA
-gets ViewModel with reference: TestViewModel@e71fab8
2) rotate device
-gets ViewModel with reference: TestViewModel@e71fab8
3) open ActivityB
4) rotate device
5) press back (finish Activity B)
6) Activity A comes back from stack
-gets ViewModel with reference: TestViewModel@260072e
I have tested with two kinds of ViewModel, with factory and default creation method:
ViewModelProviders.of(this).get(TestViewModel::class.java)
ViewModelProviders.of(this, factory).get(DashboardViewModel::class.java)
Both Activities extends AppCompatActivity class.
I have made some research and find case when this issue do not happend:
1) open ActivityA (vertical)
-gets ViewModel with reference: TestViewModel@e71fab8
2) rotate device (horizontal)
-gets ViewModel with reference: TestViewModel@e71fab8
3) open ActivityB (horizontal)
4) rotate device (vertical)
5) rotate device (horizontal)
6) press back (finish Activity B)
7) Activity A comes back from stack (horizontal)
-gets ViewModel with reference: TestViewModel@e71fab8
So when I rotate device twice on Activity B and press back the Activity A do not recreate ViewModel, mayeby it is becouse it has the same orientation as befor putting it in activities stack.
lo...@znerol.ch <lo...@znerol.ch> #10
This behavior is due to FragmentActivity.onDestroy() calling into mViewModelStore.clear() if mRetaining is false. This is the case when the device is rotated and the affected activity is in the background (and probably in other conditions outlined above as well). It is not the case if the configuration changes when the activity is in the front.
Since the view model store has ben cleared, a new view model will be created for the affected activity after a configuration change in the background.
The behavior can be observed easily using a break point in ViewModelStore.clear()
Since the view model store has ben cleared, a new view model will be created for the affected activity after a configuration change in the background.
The behavior can be observed easily using a break point in ViewModelStore.clear()
lo...@znerol.ch <lo...@znerol.ch> #11
Easy way to detect the condition from within onDestroy() on your own activity:
override fun onDestroy() {
val current = ViewModelProviders.of(this).get(VM::class.java)
super.onDestroy()
val fresh = ViewModelProviders.of(this).get(VM::class.java)
if (current != fresh) {
Log.e("Main", "View model has been destroyed")
}
}
override fun onDestroy() {
val current = ViewModelProviders.of(this).get(VM::class.java)
super.onDestroy()
val fresh = ViewModelProviders.of(this).get(VM::class.java)
if (current != fresh) {
Log.e("Main", "View model has been destroyed")
}
}
lo...@znerol.ch <lo...@znerol.ch> #12
And a dirty workaround:
override fun onDestroy() {
val current = ViewModelProviders.of(this).get(VM::class.java)
super.onDestroy()
@Suppress("UNCHECKED_CAST")
val fresh = ViewModelProviders.of(this, object : ViewModelProvider.Factory{
override fun <T : ViewModel?> create(modelClass: Class<T>) = current as T
}).get(VM::class.java)
if (current != fresh) {
Log.e("Main", "View model destroyed!")
}
}
override fun onDestroy() {
val current = ViewModelProviders.of(this).get(VM::class.java)
super.onDestroy()
@Suppress("UNCHECKED_CAST")
val fresh = ViewModelProviders.of(this, object : ViewModelProvider.Factory{
override fun <T : ViewModel?> create(modelClass: Class<T>) = current as T
}).get(VM::class.java)
if (current != fresh) {
Log.e("Main", "View model destroyed!")
}
}
no...@gmail.com <no...@gmail.com> #13
It's happening also after entering to multi-window mode
[Deleted User] <[Deleted User]> #14
Somebody put my issue https://issuetracker.google.com/issues/78095959 as a duplicate of this one. I would like to clarify, if this is really the case and if the issue that I reported will also be taken into account when fixing this issue.
Quote #6:
"Oh, another instance when platform doesn't call onSaveInstanceState, but calls onStop and onRetainNonConfiguration, so fragments are confused, we are going to fix fragments so they work around this behavior."
In the case which I presented, onSaveInstanceState IS called. But after that the activity is destroyed, so are the fragments. But ViewModel is not being cleared. There is a condition which skips clearing ViewModel after state is saved:
/**
* Called when the fragment is no longer in use. This is called
* after {@link #onStop()} and before {@link #onDetach()}.
*/
@CallSuper
public void onDestroy() {
mCalled = true;
// Use mStateSaved instead of isStateSaved() since we're past onStop()
if (mViewModelStore != null && !mHost.mFragmentManager.mStateSaved) {
mViewModelStore.clear();
}
}
But for fragments that are destroyed, loaders must be reset, like it happens in case of activity. I think the right condition for loader lifecycle to work correctly should be something like this:
if (mViewModelStore != null) {
// Do not reset loaders on activity rotation
final Activity activity = getActivity();
if (activity != null && !activity.isChangingConfigurations()) {
fragment.mViewModelStore.clear();
}
}
So loaders are always reset in Fragment.onDestroy(), unless the activity is rotated. I think this is also applicable to ViewModel.
Quote #6:
"Oh, another instance when platform doesn't call onSaveInstanceState, but calls onStop and onRetainNonConfiguration, so fragments are confused, we are going to fix fragments so they work around this behavior."
In the case which I presented, onSaveInstanceState IS called. But after that the activity is destroyed, so are the fragments. But ViewModel is not being cleared. There is a condition which skips clearing ViewModel after state is saved:
/**
* Called when the fragment is no longer in use. This is called
* after {@link #onStop()} and before {@link #onDetach()}.
*/
@CallSuper
public void onDestroy() {
mCalled = true;
// Use mStateSaved instead of isStateSaved() since we're past onStop()
if (mViewModelStore != null && !mHost.mFragmentManager.mStateSaved) {
mViewModelStore.clear();
}
}
But for fragments that are destroyed, loaders must be reset, like it happens in case of activity. I think the right condition for loader lifecycle to work correctly should be something like this:
if (mViewModelStore != null) {
// Do not reset loaders on activity rotation
final Activity activity = getActivity();
if (activity != null && !activity.isChangingConfigurations()) {
fragment.mViewModelStore.clear();
}
}
So loaders are always reset in Fragment.onDestroy(), unless the activity is rotated. I think this is also applicable to ViewModel.
ro...@gmail.com <ro...@gmail.com> #15
Was about to report this issue. I too am observing the same issue - exactly as do...@gmail.com https://issuetracker.google.com/issues/73644080#comment9 describes it. I also noted that if the ViewModel is instantiated with a Fragment - ie: mViewModel = ViewModelProviders.of(<Fragment>).get(TestViewModel.class); - the ViewModel will always get the same instance back - even in the above described scenarios.
I found a sample project on GitHub where the issue is easily reproduced as well:https://github.com/jshvarts/SimpleViewModel
I found a sample project on GitHub where the issue is easily reproduced as well:
sa...@gmail.com <sa...@gmail.com> #16
- I have a temporary solution that we have implemented using retained fragments to hold the ViewModel.
See:https://bitbucket.org/sajeevmadu/viewmodeltest for the solution.
See:https://issuetracker.google.com/issues/78788599
for a video of the issue.
See:
See:
for a video of the issue.
il...@google.com <il...@google.com>
il...@google.com <il...@google.com> #17
This is fixed internally and will be available in future releases of the fragment dependency.
mi...@gmail.com <mi...@gmail.com> #18
when and which version will this fix be included?
il...@google.com <il...@google.com> #19
The fix is available in 28.0.0-alpha3 and AndroidX 1.0.0-alpha3
ma...@mcassiano.com <ma...@mcassiano.com> #22
For anyone still following this thread: tried with 28.0.0-alpha3 and AndroidX 1.0.0-alpha3 and it doesn't happen anymore.
lb...@gmail.com <lb...@gmail.com> #23
Sadly moving to AndroidX is a huge issue for large projects. It makes so many build errors. I hope it gets fixed in future version of Android Studio
ma...@mcassiano.com <ma...@mcassiano.com> #24
If you're comfortable with alpha releases of the support library, you can raise your target to 28 and use 28.0.0-alpha3.
ta...@gmail.com <ta...@gmail.com> #25
Raising targetSdk is not needed. Only compileSdk needs to be raised to be able to use support lib 28
ma...@mcassiano.com <ma...@mcassiano.com> #26
Right, sorry about that. Realized this soon after but couldn’t edit the response :(
mo...@gmail.com <mo...@gmail.com> #27
This issue is not fixed in 2.0.0-rc01. How can you consider calling this version release candidate with this bug!
cc...@google.com <cc...@google.com> #28
Supportlib 28 and AndroidX 2.0.0-rc01 releases are not related to navigation - navigation/workmanager are still in alpha in a separate release process. They have yet to release an androidx-package version, so they aren't included included in the initial 2.0 arch components release process. See https://developer.android.com/topic/libraries/architecture/adding-components#navigation - you need to update your navigation library version specifically.
ra...@gmail.com <ra...@gmail.com> #29
I faced the same issue after calling startActivityForResult and rotate screen and then return the resulting intent. I noticed that this also caused the onCleared() of the ViewModel to be invoked in the calling activity's viewmodel.
il...@google.com <il...@google.com> #30
Re #29, if you're still having an issue with 28.0.0-alpha3 or higher (or 1.0.0-alpha2 in AndroidX), please file a new issue with a sample project that reproduces your issue.
lb...@gmail.com <lb...@gmail.com> #31
Can anyone please make a sample project to test it out, and say what are the steps?
I think the issue was fixed, at least for loaders.
I think the issue was fixed, at least for loaders.
mi...@gmail.com <mi...@gmail.com> #32
"Retained fragments to hold the ViewModel" is not a reliable solution. After reverted some requested runtime permissions, such as Storage, Location and so on, retained ViewModel will be changed to a fresh one!
ra...@gmail.com <ra...@gmail.com> #33
I'm getting a similar issue. Multiple instances on my view model are being created on screen rotate. Here's a link to my repo: https://github.com/rahulchowdhury/elly/tree/add-elephant-profile
il...@google.com <il...@google.com> #34
Re #33 - you're unconditionally adding a new Fragment in your MainActivity, meaning you're adding a new instance for every rotation. You need to surround that code with if (savedInstanceState == null).
ra...@gmail.com <ra...@gmail.com> #35
Whoops. Totally missed that one out. Sorry. 😅
lb...@gmail.com <lb...@gmail.com> #36
@34 @35 Or check if the fragment exists, using findfragmentbytag .
Description
Version used: 1.0.0-rc1
Devices/Android versions reproduced on: Nexus 5x, API 27
Sample project:
Issue on StackOverflow:
CASE-1: Log output for locking and unlocking in landscape mode
02-20 16:30:14.159 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 244798673, onCreate: orientation 2
02-20 16:30:14.159 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 244798673, onCreate: no savedInstanceState
02-20 16:30:14.169 8296-8296/com.dkarmazi.viewmodelscoping D/MyViewModel: MyViewModel 55090662: created
02-20 16:30:14.183 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 244798673, onResume: orientation 2
### LOCKED IN LANDSCAPE MODE
02-20 16:30:22.978 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 244798673, onSaveInstanceState: SAVED_STATE_244798673
02-20 16:30:22.996 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 244798673, onStop: orientation 2
### UNLOCKED IN LANDSCAPE MODE
02-20 16:30:33.177 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 244798673, onStop: orientation 2
02-20 16:30:33.178 8296-8296/com.dkarmazi.viewmodelscoping D/MyViewModel: MyViewModel 55090662: onCleared
02-20 16:30:33.179 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 244798673, onDestroy: orientation 2
02-20 16:30:33.241 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 218111434, onCreate: orientation 1
02-20 16:30:33.241 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 218111434, onCreate: saved string from savedInstanceState SAVED_STATE_244798673
02-20 16:30:33.242 8296-8296/com.dkarmazi.viewmodelscoping D/MyViewModel: MyViewModel 113479034: created
02-20 16:30:33.248 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 218111434, onResume: orientation 1
02-20 16:30:33.705 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 218111434, onSaveInstanceState: SAVED_STATE_218111434
02-20 16:30:33.710 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 218111434, onStop: orientation 1
02-20 16:30:33.712 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 218111434, onDestroy: orientation 1
02-20 16:30:33.815 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 158140230, onCreate: orientation 2
02-20 16:30:33.815 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 158140230, onCreate: saved string from savedInstanceState SAVED_STATE_218111434
02-20 16:30:33.822 8296-8296/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 158140230, onResume: orientation 2
CASE-2: Log output for portrait mode:
02-20 16:38:10.283 8567-8567/? D/FragmentActivity: Activity 244798673, onCreate: orientation 1
02-20 16:38:10.283 8567-8567/? D/FragmentActivity: Activity 244798673, onCreate: no savedInstanceState
02-20 16:38:10.293 8567-8567/? D/MyViewModel: MyViewModel 55090662: created
02-20 16:38:10.301 8567-8567/? D/FragmentActivity: Activity 244798673, onResume: orientation 1
02-20 16:38:13.459 8567-8567/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 244798673, onSaveInstanceState: SAVED_STATE_244798673
02-20 16:38:13.480 8567-8567/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 244798673, onStop: orientation 1
02-20 16:38:17.704 8567-8567/com.dkarmazi.viewmodelscoping D/FragmentActivity: Activity 244798673, onResume: orientation 1
Problem:
In CASE 1, based on the logs, the bundle get somehow properly routed from Activity 244798673 to Activity 218111434 at `02-20 16:30:33.241`, but the ViewModel is not able to persist through this sequence of actions. Also, notice how ViewModel gets destroyed in CASE-1, but persisted in CASE-2.
Expected behavior:
`ViewModel` should not be destroyed in CASE-1.