View Model. Example

In this article we will go through the steps of creating a simple game screen. We will make it the traditional way without using View Model and we will see why it is absolutely wrong to persist data in the View. After that we will refactor the example using a View Model. The design that we see is from the screen that appears after a player has finished a level. In the example we can see that the player has collected 2000 gold coins. The player can exchange the gold coins for diamonds that increase his special abilities. Each diamond costs 100 gold coins. To exchange 100 coins for 1 diamond the player must tap the plus button. The operation can be repeated until the player has no gold coins left. If he decides that he has bought too many diamonds and he needs the coins for other stuff, he can tap the minus button to take his gold back. Now it is time to write the back-end of what we see.

The design that we see above is from the screen that appears after a player has finished a level. In the example we can see that the player has collected 2000 gold coins. The player can exchange the gold coins for diamonds that increase his special abilities. Each diamond costs 100 gold coins. To exchange 100 coins for 1 diamond the player must tap the plus button. The operation can be repeated until the player has no gold coins left. If he decides that he has bought too many diamonds and he needs the coins for other stuff, he can tap the minus button to take his gold back. Now it is time to write the back-end of what we see. First we create a new Activity class. We declare three variables: the amount of coins the player has gained (in a real game this value should not be hard coded but the example is for educational purpose only), the amount of diamonds the user has bought(0 when the screen appears) and the cost per diamond(100 gold coins).

				
					public class LevelFinishedActivity extends AppCompatActivity {

    private int coins = 2000;
    private int diamonds = 0;
    private final int diamondCost = 100;
				
			

The next step is to create a method for displaying the data in the View. The method is called initUI(). In our XML file for the Activity we have declared two TextViews for displaying the coins and diamonds amount. We have also declared two buttons for buying and returning back diamonds if we decide we don’t need them. The TextViews are initialized with the values of the coins and diamonds variables. When the player presses the add button the amount of diamonds is increased by 1 and for each bought diamond the amount of coins is decreased by 100. This operation can be done until his gold is not enough to buy more diamonds. The values in the two TextViews are updated on each click. The same process is repeated for the other button.

				
					private void initUI() {
        TextView textViewCoins = findViewById(R.id.text_view_coins);
        TextView textViewDiamonds = findViewById(R.id.text_view_diamonds);
        Button buttonAdd = findViewById(R.id.button_add);
        Button buttonSubstract = findViewById(R.id.button_sub);

        textViewCoins.setText(String.valueOf(coins));
        textViewDiamonds.setText(String.valueOf(diamonds));

        buttonAdd.setOnClickListener((View view) -> {
            if(coins >= diamondCost) {
                diamonds += 1;
                coins -= diamondCost;
                textViewCoins.setText(String.valueOf(coins));
                textViewDiamonds.setText(String.valueOf(diamonds));
            }
        });

        buttonSubstract.setOnClickListener((View view) -> {
            if(diamonds > 0) {
                diamonds -= 1;
                coins += diamondCost;
                textViewCoins.setText(String.valueOf(coins));
                textViewDiamonds.setText(String.valueOf(diamonds));
            }
        });
    }
				
			

Our final step is to call the initUI() method that we just created in the onCreate().

				
					@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    initUI();
}
				
			

Now it is time to start the application and see what it looks like.

Now let‘s rotate the screen. The player may decide to rotate it, right?

Oops. That’s now what we expected to happen. The diamonds that the player has decided to buy are now 0 again. It is quite bad user experience. The reason for this is that on rotation the Activity is destroyed and recreated. Since we persist the data for the screen in the View it is lost every time the Activity is destroyed. An option that comes to your mind is probably to persist the data from the screen in some kind of internal storage. For example a SQLite database or SharedPreferences maybe. But this can be resource consuming especially if we persist the data in our onDestroy() method or even worse when a click is triggered. A better approach is to use the ViewModel class. According to the official documentation “The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations.” It seems exactly like what we need in this scenario.

The first step is to add the following dependency in the build.gradle (Module:app) file. It allows us to use the Android’s ViewModel implementation.
				
					implementation 'android.arch.lifecycle:extensions:1.1.1'
				
			

Then we create a class for our ViewModel. Let’s call it LevelFinishedViewModel. As we have learnt in the previous article, the LevelFinishedViewModel will hold the needed business logic and will expose data to the View(in this case the LevelFinishedActivity). Moreover, the LevelFinishedViewModel will outlive the LevelFinishedActivity and will survive all the configuration changes. After each recreation of the LevelFinishedActivity it will get reference to the same LevelFinishedViewModel instance, which is holding the data for the View.

				
					public class LevelFinishedViewModel extends ViewModel {
}
				
			
Notice that the LevelFinishedViewModel extends the ViewModel class which is part of the Android Architecture Components (a collection of libraries).
Then we move our private variables from the View to the ViewModel. This way we will persist them during configuration changes like locale changes and rotations of the screen.
				
					public class LevelFinishedViewModel extends ViewModel {

    private int coins = 2000;
    private int diamonds = 0;
    private final int diamondCost = 100;

}
				
			

After that we create getters for the coins and diamonds variables to expose data to the View.

				
					public class LevelFinishedViewModel extends ViewModel {

    private int coins = 2000;
    private int diamonds = 0;
    private final int diamondCost = 100;

    public int getCoins() {
        return coins;
    }

    public int getDiamonds() {
        return diamonds;
    }
    
}
				
			

Then we create two methods holding the business logic for buying a diamond and returning a diamond. Notice that we moved the logic from the View to the ViewModel(this way our View holds only presentation logic and no business logic). It actually serves its duties – to handle UI operations.

				
					public class LevelFinishedViewModel extends ViewModel {

    private int coins = 2000;
    private int diamonds = 0;
    private final int diamondCost = 100;

    public int getCoins() {
        return coins;
    }

    public int getDiamonds() {
        return diamonds;
    }

    public void buyDiamond() {
        if(coins >= diamondCost) {
            diamonds += 1;
            coins -= diamondCost;
        }
    }

    public void returnDiamond() {
        if(diamonds > 0) {
            diamonds -= 1;
            coins += diamondCost;
        }
    }

}
				
			

Now it is time to create a variable for our ViewModel in the LevelFinishedActivity.

				
					private LevelFinishedViewModel levelFinishedViewModel;
				
			

In the onCreate() method we add the following line of code. We get an instance of the LevelFinishedViewModel and we specify that the ViewModel will be present while the scope of the LevelFinishedActivity is alive.

				
					levelFinishedViewModel = ViewModelProviders.of(this).get(LevelFinishedViewModel.class);
				
			

Then in the onClick implementations we just call the corresponding methods from the ViewModel. You can see that now the code is also quite cleaner than before.

				
					 buttonAdd.setOnClickListener((View view) -> {
        levelFinishedViewModel.buyDiamond();
        textViewCoins.setText(String.valueOf(levelFinishedViewModel.getCoins()));
        textViewDiamonds.setText(String.valueOf(levelFinishedViewModel.getDiamonds()));
});

buttonSubstract.setOnClickListener((View view) -> {
        levelFinishedViewModel.returnDiamond();
        textViewCoins.setText(String.valueOf(levelFinishedViewModel.getCoins()));
        textViewDiamonds.setText(String.valueOf(levelFinishedViewModel.getDiamonds()));
});
				
			

The last thing we need to do is test what happens when a rotation occurs. Let’s buy 5 diamonds. Now we rotate the screen. And the diamonds and our remaining coins are persisted. We can continue rotating the screen and changing the values on the screen and now they are not lost.

In this tutorial we learned how to use the Android Architecture Components’ ViewModel to persist data during configuration changes.

Share :
Share :

Weitere Beiträge

View Model. Example

In this article we will go through the steps of creating a simple game screen. We will make it the traditional way without using View Model and we will see why it is absolutely wrong to persist data in the View.

Weiterlesen »