Modern Android App Architecture with Jetpack

Revision as of 19:49, 22 May 2018 by Neil (Talk | contribs)

Revision as of 19:49, 22 May 2018 by Neil (Talk | contribs)

Until recently, Google did not recommend a specific approach to building Android apps other than to provide tools and development kits while letting developers decide what worked best for a particular project or individual programming style. That changed in 2017 with the introduction of the Android Architecture Components which, in turn, became part of Android Jetpack when it was released in 2018.

The purpose of this chapter is to provide an overview of the concepts of Jetpack, Android app architecture recommendations and some of the key architecture components. Once the basics have been covered, these topics will be covered in more detail and demonstrated through practical examples in later chapters.

What is Android Jetpack?

Android Jetpack consists of Android Studio, the Android Architecture Components and Android Support Library together with a set of guidelines that recommend how an Android App should be structured. The Android Architecture Components are designed to make it quicker and easier both to perform common tasks when developing Android apps while also conforming to the key principle of the architectural guidelines.

While all of the Android Architecture Components will be covered in this book, the objective of this chapter is to introduce the key architectural guidelines together with the ViewModel, LiveData, Lifecycle components while also introducing Data Binding and the use of Repositories.

Before moving on, it is important to understand the Jetpack approach to app development is not mandatory. While highlighting some of the shortcoming of other techniques that have gained popularity of the years, Google stopped short of completely condemning those approaches to app development. Google appears to be taking the position that while there is no right or wrong way to develop an app, there is a recommended way.

The “Old” Architecture

In the chapter entitled TBD, an Android project was created consisting of a single activity which contained all of the code for presenting and managing the user interface together with the back-end logic of the app. Up until the introduction of Jetpack, the most common architecture followed this paradigm with apps consisting of multiple activities (one for each screen within the app) with each activity class to some degree mixing user interface and back-end code.

This approach led to a range of problems related to the lifecycle of an app (for example an activity is destroyed and recreated each time the user rotates the device leading to the loss of any app data that had not been saved to some form of persistent storage) as well as issues such inefficient navigation involving launching a new activity for each app screen accessed by the user.


Modern Android Architecture

At the most basic level, Google now advocates single activity apps where different screens are loaded as content within the same activity.

Modern architecture guidelines also recommend separating different areas of responsibility within an app into entirely separate modules (a concept Google refers to as “separation of concerns”). One of the keys to this approach is the ViewModel component.

The ViewModel Component

The purpose of ViewModel is to separate the user interface-related data model and logic of an app from the code responsible for actually displaying and managing the user interface and interacting with the operating system. When designed in this way, an app will consist of one or more UI Controllers, such as an activity, together with ViewModel instances responsible for handling the data needed by those controllers.

In effect, the ViewModel only knows about the data model and corresponding logic. It knows nothing about the user interface and makes no attempt to directly access or respond to events relating to views within the user interface. When a UI controller needs data to display, it simply asks the ViewModel to provide it. Similarly, when the user enters data into a view within the user interface, the UI controller passes it to the ViewModel for handling.

This separation of responsibility addresses the issues relating to the lifecycle of UI controllers. Regardless of how many times a UI controller is recreated during the lifecycle of an app, the ViewModel instances remain in memory thereby maintaining data consistency. A ViewModel used by an activity, for example, will remain in memory until the activity completely finishes which, in the single activity app, is not until the app exits.

Jetpack diagram 1.png


For all its strengths, however, the ViewModel class alone does not entirely address the issue of keeping the user interface data up to date through UI controller lifecycles. After it has been recreated due to a screen rotation or other event, for example, it is still the responsibility of the UI controller to re-populate the user interface with data from the ViewModel. It is also the responsibility of the UI controller to identify when data has changed within the ViewModel so that the user interface can be updated accordingly. Fortunately, these shortcoming are addressed by making use of the LiveData component.

The LiveData Component

Consider an app that displays realtime data such as the current price of a financial stock. The app would probably use some form of stock price web service to continuously update the data model within the ViewModel with the latest information. Obviously, this realtime data is of little use unless it is displayed to the user in a timely manner. There are only two ways that the UI controller can ensure that the latest data is displayed in the user interface. One option is for the controller to continuously check with the ViewModel to find out if the data has changed since it was last displayed. The problem with this approach, however, is that it is inefficient. To maintain the realtime nature of the data feed, the UI controller would have to run on a loop, continuously checking for the data to change.

A better solution would be for the UI controller to receive a notification when a specific data item within a ViewModel changes. This is made possible by using the LiveData component. LiveData is a data holder that allows a value to become observable . In basic terms, an observable object has the ability to notify other objects when changes to its data occur thereby solving the problem of making sure that the user interface always matches the data within the ViewModel

This means, for example, that a UI controller that is interested a ViewModel value can set up an observer which will, in turn, be notified when that value changes. In our hypothetical application, for example, the stock price would be wrapped in a LiveData object within the ViewModel and the UI controller would assign an observer to the value, declaring a method to be called when the value changes. This method will, when triggered by data change, read the updated value from the ViewModel and use it to update the user interface.

Jetpack diagram 2.png


A LiveData instance may also be declared as being mutable, allowing the observing entity to update the underlying value held within the LiveData object. The user might, for example, enter a value in the user interface that needs to overwrite the value stored in the ViewModel.

Another of the key advantages of using LiveData is that it is aware of the lifecycle state of its observers. If, for example, an activity contains a LiveData observer, the corresponding LiveData object will know when the activity’s lifecycle state changes and respond accordingly. If the activity is paused (perhaps the app is put into the background), the LiveData object will stop sending events to the observer. If the activity has just started or resumes after being paused, the LiveData object will send a LiveData event to the observer so that the activity has the most up to date value. Similarly, the LiveData instance will know when the activity is destroyed and remove the observer to free up resources.

So far, we’ve only talked about UI controllers using observers. In practice, however, an observer can be used within any object that conforms to the Jetpack approach to lifecycle management.

LiveData and Data Binding

Android Jetpack includes the Data Binding Library which allows data in a ViewModel to be mapped directly to specific views within the XML user interface layout file. In the AndroidSample project created earlier, code had to be written both to obtain references to the EditText and TextView views and to set and get the text properties to reflect data changes. Data binding allows the LiveData value stored in the ViewModel to be referenced directly within the XML layout file avoiding the need to write code to keep the layout views updated.

Jetpack diagram 3.png


Android Lifecycles

The duration from when an Android component is created to the point that it is destroyed is referred to as the lifecycle. During this lifecycle, the component will change between different lifecycle states, usually under the control of the operating system and in response to user actions. An activity, for example, will begin in the initialized state before transitioning to the created state. Once the activity is running it will switch to the started state from which it will cycle through various states including created, started, resumed and destroyed. Similarly, an app running in the foreground (in other words the app with which the user is currently interacting) will switch into TBD state when placed into the background.

Many Android Framework classes and components allow other objects to access their current state. Lifecycle observers may also be used so that an object receives notification when the lifecycle state of another object changes. This is the technique used behind the scenes by the ViewModel component to identify when an observer has restarted or been destroyed. This functionality is not limited to Android framework and architecture components and may also be built into any other classes using a set lifecycle components included with the architecture components.

Objects that are able to detect and react to lifecycle state changes in other objects are said to be lifecycle-aware, while objects that provide access to their lifecycle state are called lifecycle-owners. Lifecycles will be covered in greater detail in the chapter entitled TBD.

Repository Modules

If a ViewModel obtains data from one or more external sources (such as databases or web services) it is important to separate the code involved in handling those data sources from the ViewModel class. Failure to do this would, after all, violate the separation of concerns guidelines. To avoid mixing this functionality in with the ViewModel, Google’s architecture guidelines recommend placing this code in a separate Repository module.

A repository is not an Android architecture component, but rather a Java class created by the app developer that is responsible for interfacing with the various data sources. The class then provides an interface to the ViewModel allowing that data to be stored in the model.


Jetpack diagram 4.png


Summary

Until the last year, Google has tended not to recommend any particular approach to structuring an Android app. That has now changed with the introduction of Android Jetpack which consists of a set of tools, components, libraries and architecture guidelines. Google now recommends that an app project be divided into separate modules, each being responsible for a particular area of functionality otherwise known as “separation of concerns”.

In particular, the guidelines recommend separating the view data model of an app from the code responsible for handling the user interface. In addition, the code responsible for gathering data from data sources such as wen services or databases should be built into a separate repository module instead of being bundled with the view model.

Android Jetpack includes the Android Architecture Components which have been designed specifically to make it easier to develop apps the confirm to the recommended guidelines. This chapter has introduced the ViewModel, LiveData and Lifecycle components. These will be covered in more detail starting with the next chapter. Other architecture components not mentioned in this chapter will be covered later in the book.