There were some interesting stuff at this year’s Google I/O 17. One such session was Architecture Components - Introduction by Yigit Boyar and Luke Bergstorm. The announcement included new architecture components library.
What’s Android architecture components
A new collection of libraries that help you design robust, testable, and maintainable apps. Start with classes for managing your >UI component lifecycle and handling data persistence.
The architecture components come with lot of new components such as LiveData, ViewModel, LifecycleObserver and LifecycleOwner
and Room
Persistence library. Together these components would help developers follow the right architecture pattern with proper separation of responsibility for each layer (more on that later) and help survive configuration changes, avoid memory leaks and updating the UI in case of changes in our data.
-
LifecycleOwner - Interface for components having Lifecycle (such as Activities and Fragments).
-
LifecycleObserver - LifecycleObserver subscribes to LifecycleOwner’s lifecycle state changes and are self sufficient. It does not have to rely on Activity or fragment’s
onStart()
oronStop()
lifecycle callback to initialise and stop it. -
LiveData - It is an observable data holder, it notifies the observers in case of data changes. It is also lifecycle aware component i.e. it respects the lifecycle state of the
LifecycleOwner
(Activities or Fragments). This helps preventing memory leaks and other issues. -
ViewModel- Objects that provide data for the UI components. They does not have the references to the view and are unaffected by the lifecycle of LifecycleOwner.
-
Room - SQLite based object mapping library. Room abstracts the underlying layer of implementation details like creating and managing database and tables. Room uses annotations to generate code at compile time. Furthermore Room also supports LiveData and Rx Java 2 Flowables
MVVM Pattern using Architecture components
To demonstrate the MVVM architecture pattern, let’s create a simple android app to fetch the issues of any Github repository. In this post We will be mostly be covering ViewModel and LiveData components.
Picture from: Android developers site
We will be having the following components layers:
-
View - This layer contains UI components and is responsible for view related code such as initialising child views, displaying progress bar, receiving input from user and handling animations, etc. Example: Activities and Fragments.
-
ViewModel - ViewModels provide data to the UI components. In our case views will be using LiveData to observe the data changes in ViewModel.
-
Repository - Repositories abstract the underlying implementation of Data sources that an app can use cache and fetch the data. This abstraction is helpful in two ways: 1) the code is not dependent on the concrete implementation of data store, and 2) Because of previous point, we can swap the implementation of datastore at any time such as for testing.
-
Data Service - This layer contains the actual code that caches (by using SQLite for example) or fetches data (like by fetching data from backend api’s using retrofit).
Enough of talk. Let’s Code
It is assumed that you have some understanding of retrofit. The code described below can be found in the Github here.
Open project level build.gradle
file and add the following:
Add the retrofit 2 and architecture components dependencies to app level build.gradle
file.
Next we will create the entities.
Enter the following URL in your browser https://api.github.com/repos/square/retrofit/issues to get a list of JSON objects. Grab one one JSON object and head to the following site: jsonschema2pojo and paste the copies JSON object in the text area. Select source type as JSON
and Annotation Style as GSON
since we will be using GSON converter to convert JSON to entity.
Now click preview and grab the generated code. Create 2 entities Issue
and User
.
Next, lets create another entity ApiResponse
.
We will use ApiResponse
to communicate data from Repository to ViewModel and ultimately to Activity.
So if we get any error while fetching data from the remote api, we will set Error in the ApiResponse
, else we will set the list of Issue
objects into it.
Next let’s create Retrofit Service interface:
Next, we’ll create Repository. First lets create IssueRepository
interface
Now we’ll create IssueRepositoryImpl
which implements IssueRepository
interface. As discussed above Repositories are used to abstract the communication of rest of the code to the Data sources (such as Database or API calls). In our case IssueRepositoryImpl
will use GithubApiService
to fetch data from Github API and return the value as a LiveData
.
In the getIssues()
method we create a MutableLiveData
from the data obtained from retrofit. MutableLiveData
is the subclass of LiveData that has setValue(T)
method that can be used to modify the value it holds.
Next let’s create our ViewModel class named ListIssuesViewModel
which extends ViewModel
abstract class:
ListIssuesViewModel
will fetch the data requested by the UI from the IssueRepository. It has MediatorLiveData
mApiResponse
which is observed by the UI. MediatorLiveData
is a subclass of MutableLiveData
which allows us to observe one or more LiveData (LiveData
from Repository’s getIssues()
method in our case) and propagate the changes to it own observers (Activity in our case).
Note:
-
In case you want to have a ViewModel class with non-empty constructor, you have to create a Factory class which would create instance of you ViewModel and that Factory class has to implement
ViewModelProvider.Factory
interface. -
If you want reference to Application context in your View Model class, you can use AndroidViewModel class instead of
ViewModel
class.
Finally, create an activity which extends AppCompatActivity
class (Since LifecycleActivity
has been deprecated as of version architecture components 1.0.0-alpha9–1
and both AppCompatActivity
and Support Fragment now implement the LifecycleOwner
interface) with EditText and Recycler View.
In onCreate()
we will initialise the ViewModel, observe the MediatorLiveData
property mApiResponse
and take appropriate action to display the view.
If user initiates a new search query, we will call viewModel. loadIssues(@NonNull String user, String repo)
method with appropriate parameters.
So we now have an app which uses android recommended architecture pattern by using MVVM, LiveData and Repository. Our app also persists data across configuration changes such as screen rotations.
What’s next
Android developer’s Guide to app architecture suggests using Dagger 2 library for dependency injection and Room ORM for data persistence.