After reading the first chapter of “Clean Code” book and learning the LeBlanc’s law “Later equal never”, I decided to work on some problems in my code I was postponed for weeks. One of the interesting problems I was investigating is the problem of rotation, by rotation I mean when you rotate your phone to its side. So you may ask “what is the problem if I rotate my phone? I do that all the time and I still enjoy working on my app”. That’s my friend because the developers of your app take the time to handle the problem of rotating your phone back and forth between portrait position and landscape position. I will show you a simple example to demonstrate the problem for you.
Let’s try to build a Score app, it’s a simple app with only one activity, and it’s layout consists of a button and a text view to represent score when you press the button the score increases.
At the time I wrote this post I used this dependency
implementation "android.arch.lifecycle:extensions:1.1.1"
Add this line to gradle then sync the project.
In activity_main.xml the app layout is as follow:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.narbase.score.MainActivity"> <TextView android:id="@+id/scoreTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:textSize="100sp" /> <Button android:id="@+id/increaseScoreButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/scoreTextView" android:layout_centerHorizontal="true" android:text="@string/increase_score" /> </RelativeLayout>
Don’t forget to add the increase_score string to the string.xml.
<string name="increase_score">Increase Score</string>
For the MainActivity, it’s very straightforward, you setup onClickListener on the button and when you press the button the score will increase by one.
package com.narbase.score import android.support.v7.app.AppCompatActivity import android.os.Bundle import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { private var score = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) initiateScore() increaseScoreButton.setOnClickListener { increaseScore() } } private fun initiateScore() { scoreTextView.text = score.toString() } private fun increaseScore() { val newScore = ++score scoreTextView.text = newScore.toString() } }
The output will be as follow:
But wait!!!, why the score was reset to zero when we rotated the screen?
When you rotate the device you change the configuration of the app. Configuration changes cause the activity to be destroyed and recreated again. This behavior allows you to use a specific layout when the device is rotated to its side.
For our app, that’s mean the score will be reset back to zero every time we rotate the screen, and of course, we don’t want that behavior, we want to keep the score even if we rotate the screen. Here comes the ViewModel class in handy.
You need to hold and manage the data that related to the UI so it not affected by the activity life-cycle, that’s how ViewModel class is designed. It allows you to preserve your data during configuration changes such as screen rotations.
I personally didn’t appreciate the ViewModel enough until I encountered this issue. So how we can use ViewModel in our Score app to hold the score and not to reset it to zero every time we rotate the screen.
First, you need to make a ViewModel for your screen. I said “your screen” because in general, you need to make a ViewModel for each screen in your app. This ViewModel will hold all the data that been used in the associated screen. So basically you separate your data in the ViewModel from the code in your Activity or Fragment that display your data in the UI.
The ViewModel for Score app is as follow:
package com.narbase.score import android.arch.lifecycle.ViewModel class ScoreViewModel: ViewModel() { var score = 0 }
It’s a simple ViewModel right? It only consists of the score that we want to hold.
Now in MainActivity witch is your UI controller you need to create a member variable for the ViewModel. You need to call:
ViewModelProviders.of(<Your UI controller>).get(<Your ViewModel>.class)
Now the MainActivity became as follow:
package com.narbase.score import android.arch.lifecycle.ViewModelProviders import android.support.v7.app.AppCompatActivity import android.os.Bundle import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { private val viewModel by lazy { obtainViewModel() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) initiateScore() increaseScoreButton.setOnClickListener { increaseScore() } } private fun obtainViewModel() = ViewModelProviders.of(this).get(ScoreViewModel::class.java) private fun initiateScore() { scoreTextView.text = (viewModel.score).toString() } private fun increaseScore() { val newScore = ++(viewModel.score) scoreTextView.text = newScore.toString() } }
As you can notice we used the score value that was held in the ViewModel and whenever the activity is been recreated the score sets to the last value stored in the score value in the ViewModel and not being reset back to zero as before.
The output of the app will be as follow:
And yes that is the behavior that we want when you rotate the screen, the score is not affected.
This post was inspired by Lyla Fujiwara’s post on Medium that helped me to solve the orientation changes problem. She is an Android Developer Advocate at Google. I encourage you can to check her post for more information.
Here’s a Youtube video where Lyla talks about ViewModel.
You can find the project on my GitHub page.
We will talk about the ViewModel in more details in the future, but for now, that’s it for this week. If you have any questions, comments or feedback please let’s know in the comments.
Till next week.