33. An Android Jetpack ViewModel Tutorial

The previous chapter introduced the key concepts of Android Jetpack and outlined the basics of modern Android app architecture. Jetpack essentially defines a set of recommendations describing how an Android app project should be structured while providing a set of libraries and components that make it easier to conform with these guidelines with the goal of developing reliable apps with less coding and fewer errors.

To help re-enforce and clarify the information provided in the previous chapter, this chapter will step through the creation of an example app project that makes use of the ViewModel component. This example will be further enhanced in the next chapter with the inclusion of LiveData and data binding support.

33.1 About the Project

In the chapter entitled “Creating an Example Android App in Android Studio”, a project named AndroidSample was created in which all of the code for the app was bundled into the main Activity class file. In the chapter that followed, an AVD emulator was created and used to run the app. While the app was running, we experienced first-hand the kind of problems that occur when developing apps in this way when the data displayed on a TextView widget was lost during a device rotation.

This chapter will implement the same currency converter app, this time using the ViewModel component and following the Google app architecture guidelines to avoid Activity lifecycle complications.

33.2 Creating the ViewModel Example Project

The first step in this exercise is to create the new project. Begin by launching Android Studio and, if necessary, closing any currently open projects using the File -> Close Project menu option so that the Welcome screen appears.

When the AndroidSample project was created, the Empty Activity template was chosen as the basis for the project. For this project, however, the Fragment + ViewModel template will be used. This will generate an Android Studio project structured to conform to the architectural guidelines.

Select the Create New Project quick start option from the welcome screen and, within the resulting new project dialog, choose the Fragment + ViewModel template before clicking on the Next button.

Enter ViewModelDemo into the Name field and specify com.ebookfrenzy.viewmodeldemo as the package name. Before clicking on the Finish button, change the Minimum API level setting to API 26: Android 8.0 (Oreo) and the Language menu to Java. Edit the build.gradle (Module: ViewModelDemo) file, enable view binding and click on the Sync Now link at the top of the editor panel:

android {

 

    buildFeatures {

        viewBinding true

    }

33.3 Reviewing the Project

When a project is created using the Fragment + ViewModel template, the structure of the project differs in a number of ways from the Empty Activity used when the AndroidSample project was created. The key components of the project are as follows:

33.3.1 The Main Activity

The first point to note is that the user interface of the main activity has been structured so as to allow a single activity to act as a container for all of the screens that will eventually be needed for the completed app. The main user interface layout for the activity is contained within the app -> res -> layout -> main_activity.xml file and provides an empty container space in the form of a FrameLayout (highlighted in Figure 33-1) in which screen content will appear:

Figure 33-1

33.3.2 The Content Fragment

The FrameLayout container is just a placeholder which will be replaced at runtime by the content of the first screen that is to appear when the app launches. This content will typically take the form of a Fragment consisting of an XML layout resource file and corresponding class file. In fact, when the project was created, Android Studio created an initial fragment for this very purpose. The layout resource file for this fragment can be found at app -> res -> layout -> main_fragment.xml and will appear as shown in Figure 33-2 when loaded into the layout editor:

Figure 33-2

By default, the fragment simply contains a TextView displaying text which reads “MainFragment” but is otherwise ready to be modified to contain the layout of the first app screen. It is worth taking some time at this point to look at the code that has already been generated by Android Studio to display this fragment within the activity container area.

The process of replacing the FrameLayout placeholder with the fragment begins in the MainActivity class file (app -> java -> <package name> -> MainActivity). The key lines of code appear within the onCreate() method of this class and replace the object with the id of container (which has already been assigned to the FrameLayout placeholder view) with the MainFragment class:

@Override

protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.main_activity);

    if (savedInstanceState == null) {

        getSupportFragmentManager().beginTransaction()

                .replace(R.id.container, MainFragment.newInstance())

                .commitNow();

    }

}

The code that accompanies the fragment can be found in the MainFragment.java file (app -> <package name> -> ui.main -> MainFragment). Within this class file is the onCreateView() method which is called when the fragment is created. This method inflates the main_fragment.xml layout file so that it is displayed within the container area of the main activity layout:

@Override

public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

    return inflater.inflate(R.layout.main_fragment, container, false);

}

33.3.3 The ViewModel

The ViewModel for the activity is contained within the MainViewModel.java class file located at app -> java -> ui.main -> MainViewModel. This is declared as a sub-class of the ViewModel Android architecture component class and is ready to be modified to store the data model for the app:

package com.ebookfrenzy.viewmodeldemo.ui.main;

 

import androidx.lifecycle.ViewModel;

 

public class MainViewModel extends ViewModel {

    // TODO: Implement the ViewModel

}

33.4 Designing the Fragment Layout

The next step is to design the layout of the fragment. Locate the main_fragment.xml file in the Project tool window and double click on it to load it into the layout editor. Once the layout has loaded, select the existing TextView widget and use the Attributes tool window to change the id property to resultText.

Drag a Number (Decimal) view from the palette and position it above the existing TextView. With the view selected in the layout refer to the Attributes tool window and change the id to dollarText.

Drag a Button widget onto the layout so that it is positioned below the TextView, and change the text attribute to read “Convert”. With the button still selected, change the id property to convertButton. At this point, the layout should resemble that illustrated in Figure 33-3 (note that the three views have been constrained using a vertical chain):

Figure 33-3

Finally, click on the warning icon in the top right-hand corner of the layout editor and convert the hardcoded strings to resources.

33.5 Implementing the View Model

With the user interface layout completed, the data model for the app needs to be created within the view model. Within the Project tool window, locate the MainViewModel.java file, double-click on it to load it into the code editor and modify the class so that it reads as follows:

package com.ebookfrenzy.viewmodeldemo.ui.main;

 

import androidx.lifecycle.ViewModel;

 

public class MainViewModel extends ViewModel {

 

    private static final Float rate = 0.74F;

    private String dollarText = "";

    private Float result = 0F;

 

    public void setAmount(String value) {

        this.dollarText = value;

        result = Float.parseFloat(dollarText)*rate;

    }

 

    public Float getResult()

    {

        return result;

    }

}

The class declares variables to store the current dollar string value and the converted amount together with getter and setter methods to provide access to those data values. When called, the setAmount() method takes as an argument the current dollar amount and stores it in the local dollarText variable. The dollar string value is converted to a floating point number, multiplied by a fictitious exchange rate and the resulting euro value stored in the result variable. The getResult() method, on the other hand, simply returns the current value assigned to the result variable.

33.6 Associating the Fragment with the View Model

Clearly, there needs to be some way for the fragment to obtain a reference to the ViewModel in order to be able to access the model and observe data changes. A Fragment or Activity maintains references to the ViewModels on which it relies for data using an instance of the ViewModelProvider class.

A ViewModelProvider instance is created using the ViewModelProvider class from within the Fragment. When called, the class initializer is passed a reference to the current Fragment or Activity and returns a ViewModelProvider instance as follows:

ViewModelProvider viewModelProvider = new ViewModelProvider(this);

Once the ViewModelProvider instance has been created, the get() method can be called on that instance passing through the class of specific ViewModel that is required. The provider will then either create a new instance of that ViewModel class, or return an existing instance:

ViewModel viewModel = viewModelProvider.get(MainViewModel.class);

Edit the MainFragment.java file and verify that Android Studio has already included this step within the onActivityCreated() method (albeit performing the operation in a single line of code for brevity):

viewModel = new ViewModelProvider(this).get(MainViewModel.class);

With access to the model view, code can now be added to the Fragment to begin working with the data model.

33.7 Modifying the Fragment

The fragment class now needs to be updated to react to button clicks and to interact with the data values stored in the ViewModel. The class will also need references to the three views in the user interface layout to react to button clicks, extract the current dollar value and to display the converted currency amount.

In the chapter entitled “Creating an Example Android App in Android Studio”, the onClick property of the Button widget was used to designate the method to be called when the button is clicked by the user. Unfortunately, this property is only able to call methods on an Activity and cannot be used to call a method in a Fragment. To get around this limitation, we will need to add some code to the Fragment class to set up an onClick listener on the button. The code to do this can be added to the onActivityCreated() method of the MainFragment.java file as outlined below. While making these changes, we will also convert the fragment so that it uses view binding:

.

.

import com.ebookfrenzy.viewmodeldemo.databinding.MainFragmentBinding;

 

public class MainFragment extends Fragment {

    private MainViewModel mViewModel;

    private MainFragmentBinding binding;

 

    public static MainFragment newInstance() {

        return new MainFragment();

    }

 

    @Nullable

    @Override

    public View onCreateView(@NonNull LayoutInflater inflater,

                       @Nullable ViewGroup container,

                             @Nullable Bundle savedInstanceState) {

        binding = MainFragmentBinding.inflate(inflater, container, false);

        return binding.getRoot();

    }

 

    @Override

    public void onDestroyView() {

        super.onDestroyView();

        binding = null;

    }

    

    @Override

    public void onActivityCreated(@Nullable Bundle savedInstanceState) {

        super.onActivityCreated(savedInstanceState);

        mViewModel = new ViewModelProvider(this).get(MainViewModel.class);

 

        binding.convertButton.setOnClickListener(new View.OnClickListener()

        {

            @Override

            public void onClick(View v) {

 

            }

        });

    }

.

.

}

With the listener added, any code placed within the onClick() method will be called whenever the button is clicked by the user.

33.8 Accessing the ViewModel Data

When the button is clicked, the onClick() method needs to read the current value from the EditText view, confirm that the field is not empty and then call the setAmount() method of the ViewModel instance. The method will then need to call the ViewModel’s getResult() method and display the converted value on the TextView widget.

Since LiveData is not yet being used in the project, it will also be necessary to get the latest result value from the ViewModel each time the Fragment is created.

Remaining in the MainFragment.java file, implement these requirements as follows in the onActivityCreated() method:

.

.

import java.util.Locale;

.

.

@Override

public void onActivityCreated(@Nullable Bundle savedInstanceState) {

    super.onActivityCreated(savedInstanceState);

    mViewModel = new ViewModelProvider(this).get(MainViewModel.class);

 

    binding.resultText.setText(String.format(Locale.ENGLISH,"%.2f",

                                          mViewModel.getResult()));

 

    binding.convertButton.setOnClickListener(new View.OnClickListener()

    {

        @Override

        public void onClick(View v) {

 

           if (!binding.dollarText.getText().toString().equals("")) {

                mViewModel.setAmount(String.format(Locale.ENGLISH,"%s",

                                       binding.dollarText.getText()));

                binding.resultText.setText(String.format(Locale.ENGLISH,"%.2f",

                                       mViewModel.getResult()));

            } else {

                binding.resultText.setText("No Value");

            }

        }

    });

}

33.9 Testing the Project

With this phase of the project development completed, build and run the app on the simulator or a physical device, enter a dollar value and click on the Convert button. The converted amount should appear on the TextView indicating that the UI controller and ViewModel re-structuring appears to be working as expected.

When the original AndroidSample app was run, rotating the device caused the value displayed on the resultText TextView widget to be lost. Repeat this test now with the ViewModelDemo app and note that the current euro value is retained after the rotation. This is because the ViewModel remained in memory as the Fragment was destroyed and recreated and code was added to the onActivityCreated() method to update the TextView with the result data value from the ViewModel each time the Fragment re-started.

While this is an improvement on the original AndroidSample app, there is much more that can be achieved to simplify the project by making use of LiveData and data binding, both of which are the topics of the next chapters.

33.10 Summary

In this chapter we revisited the AndroidSample project created earlier in the book and created a new version of the project structured to comply with the Android Jetpack architectural guidelines. The chapter outlined the structure of the Fragment + ViewModel project template and explained the concept of basing an app on a single Activity using Fragments to present different screens within a single Activity layout. The example project also demonstrated the use of ViewModels to separate data handling from user interface related code. Finally, the chapter showed how the ViewModel approach avoids some of the problems of handling Fragment and Activity lifecycles.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
44.211.35.130