Guest article from Google I \ O 2017 participant and one of the leaders of
GDG Kazan - Artur Vasilov (
@Arturka ).
May 19, ended the conference Google I / O. According to the opinion, in spite of the fact that not so many new products were presented this year, from a technical point of view the conference turned out to be interesting.
The biggest and most interesting technical update for me personally was the new Architecture Components (not Android O, in which there is not much interesting, and certainly not Kotlin). Google did what it should have done a long time ago - to develop standards for the architecture and provide them to the developers. Well, better late than never, and let's understand how useful Google's architecture can be.
')

First impression
Personally, the title of the reports didn’t interest me so much that I didn’t even visit them, because I didn’t believe that Google could do something interesting in this area, and not just repeat Mosby / Moxy / <your own bike>. The same skepticism persisted after a quick scan of the developed libraries. However, after some time I had to change my opinion, because:
- Google really made a good library with a convenient API;
- Google did not close, but took into account all modern developments in the field of the architecture of Android applications, taking the best of all;
- Beginners finally have good documentation that will help them start developing applications with a good architecture, without having to thoroughly understand the meaning of MVP / MVVM, etc.
After a deeper immersion, I tried to write an application using this architecture (or rather, rewrite it to assess the complexity of migration), and now I can share this experience.
Good architecture
To begin, consider what is generally meant by a good application architecture. In order not to go into philosophical statements about why architecture is needed at all, and so on, let us immediately designate the requirements for a good application architecture:
- Allows to decompose logic (data acquisition / processing / user interaction, etc.);
- Scalable. The word that should be in any article about architecture. In Android, this is quite simple, since any application can be divided into screens, between which there will be very transparent connections, and making the architecture of a separate screen scalable does not seem difficult;
- Allows you to write tests for the business logic of the application.
All these are general requirements, but in addition to them, it is possible to identify quite specific tasks that should be solved without problems within a specific architecture:
- Processing re-creation Activity. Probably the most distressful topic in building the architecture of the application, which does not need comments;
- Execution of HTTP requests (both for receiving data and sending);
- Support for data streams, for example, when working with sockets / sensors;
- Data caching;
- Error processing.
Of course, points 2-5 should be carried out taking into account the first point.
For quite a long time, Android developers independently solved all these problems, but gradually accumulated experience led to the emergence of
Clean Architecture . Clean Architecture's approaches are very good, except that it is often inappropriate in an application to divide logic into UI and business (since one of the layers may contain almost no code), so many developers use an architecture that can be represented by the following scheme (slightly rearranging blocks from Clean Architecture):

The only thing that does not take into account the problems of the life cycle, which all solve either
independently or with the help of any libraries. Now let's see what Google has offered us.
Google architecture
Just look at the scheme from Google:

Perhaps it seemed to me, but in my opinion, the previous scheme was simply turned vertically. And I don’t want to say that Google has appropriated something old, on the contrary, it shows that Google is eyeing the developers' opinion and takes into account all their achievements, which is good news.
Let us consider an example of the implementation of a single screen with a single request in terms of the proposed architecture, and at the same time we consider the key classes.
One of the big problems in Android is the need to constantly subscribe / unsubscribe from any objects when calling life-cycle methods. And since lifecycle methods are only in Activity / Fragment, all such objects (for example, GoogleApiClient, LocationManager, SensorManager and others) should be located only in Activity / Fragment, which leads to a large number of lines of code in these files.
To solve this and other problems, Google suggested using the class
LiveData - a class that is closely related to the life cycle and which implements the Observer pattern. By and large, this is a very simple class that encapsulates the work with a single object, which can be changed and which changes can be monitored. And this is again a fairly standard approach.
Consider how we can use LiveData, for example, in order to monitor the user's location changes. Create the following class:
public class LocationLiveData extends LiveData<Location> { private LocationManager locationManager; private LocationListener listener = new LocationListener(); private LocationLiveData(@NonNull Context context) { locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); } @Override protected void onActive() { locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener); } @Override protected void onInactive() { locationManager.removeUpdates(listener); } }
And now we can write the following code in the Activity:
public class LocationActivity extends LifecycleActivity { private LocationLiveData locationLiveData; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_movies);
All the necessary lifecycle methods in LiveData will be invoked by the first parameter in the observe method - LifecycleOwner. This allows us to know when to subscribe and unsubscribe from various objects (for example, using the onActive and onInactive methods).
LifecycleActivity is a class from the library that implements the LifecycleOwner interface. Unfortunately, it is for some reason inherited from FragmentActivity, and not from AppCompatActivity, and if you want to inherit from AppCompatActivity, you will have to write some code yourself:
public class BaseLifecycleActivity extends AppCompatActivity implements LifecycleRegistryOwner { @NonNull private final LifecycleRegistry lifecycleRegistry = new LifecycleRegistry(this); @MainThread @NonNull @Override public LifecycleRegistry getLifecycle() { return lifecycleRegistry; } }
In addition to the onActive and onInactive methods in LiveData, you can get callbacks on any lifecycle method.
Exactly the same model can be used to implement server queries. That is, we do not in any way change the way we get the data (also through the Repository), but we delegate the data to LiveData, which is essentially a binding for the View.
And now the main question arises - how to deal with the problem of the life cycle? Of course, Google thought about this and gave developers a component that is experiencing a re-creation of Activity -
ViewModel .
A few words about the ViewModel:
- All ViewModel instances are stored in a retain Fragment (HolderFragment). This is a very standard solution, which was also used by many developers.
- The ViewModel class should store all instances of LiveData so that they survive the re-creation of the Activity.
- The ViewModel is associated with the View via LiveData, that is, the ViewModel does not explicitly control the View, and this can be considered a variation of the MVVM pattern.
Let us analyze how we can implement a server request for a screen. Create a ViewModel that will control the screen logic:
public class MoviesViewModel extends ViewModel { @NonNull private final MoviesRepository moviesRepository; @Nullable private MutableLiveData<List<Movie>> moviesLiveData; public MoviesViewModel(@NonNull MoviesRepository moviesRepository) { this.moviesRepository = moviesRepository; } @MainThread @NonNull LiveData<List<Movie>> getMoviesList() { if (moviesLiveData == null) { moviesLiveData = new MutableLiveData<>(); moviesRepository.popularMovies() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(moviesLiveData::setValue); } return moviesLiveData; } }
Then View can use this ViewModel as follows:
viewModel.getMoviesList().observe(this, movies -> { if (movies != null) { adapter.setNewValues(movies); } });
Since the ViewModel is experiencing a re-creation of Activity, LiveData will be created only once and the server request will be executed only once, that is, these problems are solved. There remains the question of creating a ViewModel.
To access the ViewModel, use the ViewModelProviders class:
MoviesViewModel viewModel = ViewModelProviders.of(this).get(MoviesViewModel.class);
However, there is a small problem. When we use a non-default constructor (and in the example above, we passed a Repository object to the constructor), we will have to write our factory to create a ViewModel:
class MoviesViewModelProviderFactory extends BaseViewModelFactory { @NonNull private final MoviesRepository repository; MoviesViewModelProviderFactory(@NonNull MoviesService moviesService) { this.repository = new MoviesRepository(moviesService); } @NonNull @Override public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { if (modelClass.equals(MoviesViewModel.class)) {
And now we can create a ViewModel as follows:
ViewModelProvider.Factory factory = new MoviesViewModelProviderFactory(service); return ViewModelProviders.of(this, factory).get(MoviesViewModel.class);
In this example, we manually transfer to the Factory all the necessary objects to create a ViewModel, but the only normal approach for creating a ViewModel with an increase in the number of parameters is Dagger. This is where Google actually forces you to use it. Good or bad, you decide.
And this is all - we received the data, displayed them, and all this with the processing of the life cycle. However, there are a few more points that we will look at next.
Progress display and error handling
When I just started to deal with new architectural components, I immediately had a question about how to handle errors using LiveData or get a change in progress. After all, all that we have is an instance of one class, which we must transfer to Activity / Fragment. A similar question arises with the display of progress. Such a problem is a bit like a problem with loaders, however there are two options for how to solve it.
First, we can create some class whose object will encapsulate the status of the request, the error message and the data itself, and pass this object to the Activity / Fragment. This method is recommended in the documentation. Such a class might look, for example, as follows (for now, we only analyze error handling):
public class Response<T> { @Nullable private final T data; @Nullable private final Throwable error; private Response(@Nullable T data, @Nullable Throwable error) { this.data = data; this.error = error; } @NonNull public static <T> Response<T> success(@NonNull T data) { return new Response<T>(data, null); } @NonNull public static <T> Response<T> error(@NonNull Throwable error) { return new Response<T>(null, error); } @Nullable public T getData() { return data; } @Nullable public Throwable getError() { return error; } }
Then with each change we pass to the View an object of this class:
public class MoviesViewModel extends ViewModel { @Nullable private MutableLiveData<Response<List<Movie>>> moviesLiveData;
And View processes it accordingly:
viewModel.getMoviesList().observe(this, moviesResponse -> { if (moviesResponse != null && moviesResponse.getData() != null) { adapter.setNewValues(moviesResponse.getData()); } else if (moviesResponse != null && moviesResponse.getError() != null) {
And secondly, there is a way that in my opinion is more concise. In the end, the request status and errors are also some changing data. Then why not put them in LiveData and not subscribe to their changes? For these purposes, we can write the following code (and in this situation we will consider processing the status of the load):
@NonNull private final MutableLiveData<Boolean> loadingLiveData = new MutableLiveData<>(); @NonNull LiveData<Boolean> isLoading() { return loadingLiveData; } @MainThread @NonNull LiveData<Response<List<Movie>>> getMoviesList() { if (moviesLiveData == null) { moviesLiveData = new MutableLiveData<>(); moviesRepository.popularMovies()
And now View will subscribe not to one LiveData, but to several. This can be very convenient if we write a general handler for displaying progress or errors):
viewModel.isLoading().observe(this, isLoading -> { if (isLoading != null && isLoading) { progressDialog.show(getSupportFragmentManager()); } else { progressDialog.cancel(); } });
As a result, we can say that error handling with LiveData is almost as good as with the usual RxJava.
Testing
As mentioned above, a good architecture should allow testing an application. Since the architecture does not carry any fundamental changes from Google, testing can only be viewed at the ViewModel level (since we know how to test Repository / UI).
Testing the ViewModel is always a bit more complicated than testing the Presenter from the MVP pattern, since in MVP the explicit connection between View and Presenter makes it easy to check that the right methods were called on View. In the case of MVVM, we have to check the work of the bindings or LiveData.
Create a test class in which in the setUp method we create a ViewModel:
@RunWith(JUnit4.class) public class MoviesViewModelTest { private MoviesViewModel viewModel; private MoviesRepository repository; @Before public void setUp() throws Exception { repository = Mockito.mock(MoviesRepository.class); Mockito.when(repository.popularMovies()).thenReturn(Observable.just(new ArrayList<>())); viewModel = new MoviesViewModel(repository); } }
To test the correctness of the ViewModel, we need to make sure that it executes the request to the Repository and sends the data to LiveData:
@Test public void testLoadingMovies() throws Exception { Observer observer = Mockito.mock(Observer.class); viewModel.getMoviesList().observeForever(observer); Mockito.verify(repository).popularMovies(); Mockito.verify(observer).onChanged(any(Response.class)); }
Here we omit the extra details, but if necessary, you can use the capabilities of the Mockito to make sure that the data you need came to LiveData.
However, such a test will not work, since the setValue method in the LiveData class checks that it is called in the main application thread (of course, using the Looper):
@MainThread protected void setValue(T value) { assertMainThread("setValue"); mVersion++; mData = value; dispatchingValue(null); }
As we well know, tests on JUnit do not like this and fall. But Google developers have foreseen this. To do this, you need to connect an additional library for tests and add a rule to the test:
testCompile ("android.arch.core:core-testing:$architectureVersion", { exclude group: 'com.android.support', module: 'support-compat' exclude group: 'com.android.support', module: 'support-annotations' exclude group: 'com.android.support', module: 'support-core-utils' }) @Rule public InstantTaskExecutorRule instantExecutorRule = new InstantTaskExecutorRule();
Similarly, we can test the display of progress during data loading:
@Test public void testProgressUpdated() throws Exception { Observer observer = Mockito.mock(Observer.class); viewModel.isLoading().observeForever(observer); viewModel.getMoviesList(); Mockito.verify(observer).onChanged(true); Mockito.verify(observer).onChanged(false); }
Thus, we can write tests for all the necessary components without any special problems.
Data caching
Strictly speaking, data caching does not change at all with this architecture change, because we all also use a repository that hides all the details of receiving and saving data. However, data caching is worth mentioning in connection with the new library for working with the database - Room.
Everyone knows about the existing huge zoo database libraries in Android. These are
Realm ,
greenDao ,
ObjectBox ,
DBFlow ,
SQLBrite and many others. Therefore, many developers do not know what to choose for their project, and here Room is the ideal option, at least because it is from Google.
Room is a fairly simple library that runs on top of SQLite, uses annotations to generate boilerplate code and has a fairly familiar and convenient API. However, I do not think that I can tell you about Room something more interesting than it was in the
session on Google I / O and what is in the
documentation . Therefore, here we will finish the consideration of architectural innovations.
Conclusion
In general, Google is doing the right thing, and their architecture is really very thoughtful and convenient. So you can start using it right now, at least worth trying in new projects. A small example by which the article was written and which, perhaps, will help you try a new architecture from Google, is available on
Github . Also, of course, it is worth looking at
examples from Google.