Two ways of saving and retrieving data when your screen rotates
- Ruben Mim
- 1 juin 2020
- 3 min de lecture
Dernière mise à jour : 2 juin 2020
When we rotate our device, and the orientation changes something messy happens : your activity get destroyed. You'll answer me "the device still shows it, are you blind ?!" and you will be right. What happen is the activity calls on create() again after rotation and puf ! It's still displayed ! But the current data you were manipulating may be erased. Annoying isn't it ?
In this tutorial we are going to look at two different ways to save and retrieve data when the screen has rotated. The first and old one will be using onSaveInstanceState() and onRestoreInstanceState() methods. The second one, I personally like to use is saving the data with a ViewModel.
Good old fashion
class MainActivity : AppCompatActivity() {
// 1 we create some test variables
var test = "test"
var savedTest = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//2 we print the variable every time on create is called
Log.i("test create", test )
//3 we print the saved bundle if its not null
Log.i("bundle saved on create", savedInstanceState?.getString("test") ?: "")
//4 To modify our test variable we use an editText with its
listener ( for the example )
editText.addTextChangedListener(object : TextWatcher{
override fun afterTextChanged(s: Editable?) {}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { }
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
//5 On textChanged() we set the new value to ours
test = s.toString()
}
})
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
//5 Here we insert our value in the device memory as a bundle,
meaning using a key, value storage
outState.putString("test", test)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
//6 To retrieve the value AFTER ROTATION and after onStart(), we use our bundle
package and look for the required to get the value.
" ?: "" " sets your value to "", to avoid any
nullPointerException if it doesn't exist.
savedTest = savedInstanceState.getString("test") ?: ""
Log.i("saved test after start", savedTest)
super.onRestoreInstanceState(savedInstanceState)
}
override fun onStart() {
super.onStart()
Log.i("start " ,"start")
}
}
In the console after rotation of your device you are supposed to get this result :
I/test create: test
I/bundle saved on create: lol
I/start: start
I/saved test after start: lol
Here we can deduce several things : first things first, the activity called onCreate() again, proofed with the "bundle saved on create" log. Then onRestoreInstanceState() is called after onStart(). Consequently, if you want to get the data before onStart() you don't need to use this method and save some lines of code.
Also if you paid attention to your console before the rotation, our bundle, being empty at our first run (not printed at all in the console) is now filled with the value gotten from the bundle package saved in onSaveInstanceState() , isn't it magic ?
The savedInstanceState parameter in the onCreate() is a reference to a Bundle object that is passed from onSaveInstanceState() .
The ViewModel way

To be able to take this path, we need a SaveStateHandle object so we are going to implement this in our gaddle file :
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.0-alpha03"
Then of course we will create a ViewModel class with our brand new SaveStateHandle as a parameter :
//1 As we said earlier we set our implemented object as a parameter
class SaveViewModel(state : SavedStateHandle) : ViewModel(){
companion object{
private const val ID = "userID"
}
private val savedStateHandle = state
//2 manage data mutation
private val user_id : MutableLiveData<String> = savedStateHandle.getLiveData(ID)
fun saveCurrentString( userId : String){
//3 Sets a new value for the object associated to the key.
savedStateHandle.set(ID, userId)
}
fun getCurrentUser() : String {
//4 Gets the current value of the user id from the saved state
handle
return savedStateHandle.get(ID)?: ""
}
}
Now let's set up our Activity :
class MainActivity : AppCompatActivity() {
var viewModel : SaveViewModel? = null
var test = "test"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//1 Instanciating our view model with the SavedStateViewModelFactory
viewModel = ViewModelProvider(this, SavedStateViewModelFactory(application, this))
.get(SaveViewModel::class.java)
//2 As we have done previously here we test our values in the onCreate
method
Log.i("test create", test )
Log.i("saved test create", viewModel!!.getCurrentUser())
userid.addTextChangedListener(object : TextWatcher{
override fun afterTextChanged(s: Editable?) {
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
//3 We save the value through the view model
viewModel!!.saveCurrentString(s.toString())
Log.i("test",s.toString())
}
})
}
}
And here is the result in the console after rotation :
I/test create: test
I/saved test create: lol
I/start test: lol
I/test: lol
Here the main difference is, the saved value exists already in onCreate method after recreating the activity.
We can deduce our view model is not dying when the activity is rotating , WOW !
Comments