I have a pretty simple app with a dummy Activity
and dummy Android Lifecycle ViewModel ViewModel
.
FragmentActivity
class FragmentActivity: AppCompatActivity() {
companion object {
private const val TAG = "FragmentActivity"
private const val KEY = "key_key"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_fragment)
Log.d(TAG, "Activity ${hashCode()}, onCreate: orientation ${resources.configuration.orientation}")
if (savedInstanceState != null) {
Log.d(TAG, "Activity ${hashCode()}, onCreate: saved string from savedInstanceState ${savedInstanceState.getString(KEY)}")
} else {
Log.d(TAG, "Activity ${hashCode()}, onCreate: no savedInstanceState")
}
val myViewModel: MyViewModel = ViewModelProviders
.of(this, VmFactory())
.get(MyViewModel::class.java)
}
override fun onResume() {
super.onResume()
Log.d(TAG, "Activity ${hashCode()}, onResume: orientation ${resources.configuration.orientation}")
}
override fun onStop() {
super.onStop()
Log.d(TAG, "Activity ${hashCode()}, onStop: orientation ${resources.configuration.orientation}")
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "Activity ${hashCode()}, onDestroy: orientation ${resources.configuration.orientation}")
}
override fun onSaveInstanceState(outState: Bundle?) {
val savedString = "SAVED_STATE_" + hashCode()
outState?.putString(KEY, savedString)
Log.d(TAG, "Activity ${hashCode()}, onSaveInstanceState: $savedString")
super.onSaveInstanceState(outState)
}
}
ViewModel
class MyViewModel: ViewModel() {
companion object {
private const val TAG = "MyViewModel"
}
init {
Log.d(TAG, "MyViewModel ${hashCode()}: created")
}
override fun onCleared() {
Log.d(TAG, "MyViewModel ${hashCode()}: onCleared")
super.onCleared()
}
}
ViewModelFactory
class VmFactory: ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass == MyViewModel::class.java) {
return MyViewModel() as T
} else {
throw IllegalArgumentException()
}
}
}
Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.dkarmazi.unknownmemorysampleapp">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".WebViewActivity">
</activity>
<activity android:name=".FragmentActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Steps to kill the ViewModel
- Put the app to landscape mode
- Lock the screen
- Unlock the screen and observe that ViewModel is gone, Activity destroyed and created two times.
Log output
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
This behavior is consistent with findings here, however, I'd expect the arch library to handle this case as it is pretty standard and locking and unlocking in portrait mode works as expected.
Any good ideas on preventing that ViewModel
from being destroyed in this particular scenario?
Tested on Nexus 5X, API 27
EDIT 1: after adding a string to be saved in onSaveInstanceState
and checking if that string persists through all activity destroys and creates, I'm pretty sure that this is a bug with the library.
EDIT 2: Why is this a problem?
Problem 1: In case of landscape locking, the bundle gets somehow properly routed from Activity 244798673 to Activity 218111434 at 02-20 16:30:33.241
, however the ViewModel
is not able to persist through this sequence of actions. This is inconsistent with bundles behavior as we're technically still within the same Activity scope.
Problem 2: Log output for locking and unlocking in 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
ViewModel
is persisted in portrait locking and unlocking which is inconsistent with landscape scenario.