Why does architecture matter? An application's architecture defines its individual components, the functions they perform, and the relationships between them. It provides a clear division of responsibilities and low coupling of components, which in turn translates into ease of maintenance, good scalability and code reusability. The aforementioned benefits apply to both small and large solutions and are independent of the technology used. The decision of which architecture to use is extremely important, and changing it later is very costly. Therefore, those responsible for its planning should have extensive knowledge of the technologies used, tools and commonly accepted practices and standards.
The complexity of Android applications
Anyone who has written an application on the Android platform at least once knows that it is not a trivial task. There are many non-business aspects that need to be taken into account during the process, e.g. the high fragmentation of available devices, the need to maintain backward compatibility, the complexity of the API, the variety of components, efficient resource management, state management and its synchronization, multithreading and many others. All this makes the delivery of a simple application require extensive knowledge, as well as a relatively large amount of work. Another aspect to be addressed is the functional scope itself. Initially, mobile apps usually performed one very well-defined task, e.g. showing the current weather. Nowadays, they are highly developed and often resemble applications known from desktop platforms. These factors force the need for an architecture that will bring complexity under control and give a clear picture of what is happening in the application.
Android application architecture
An increasing number of developers are realizing the importance of using an architecture. However, the official documentation does not impose a specific solution --- there is a lack of guidance and patterns. Looking for information on the web, one can identify three very popular approaches --- "classic", MVP and MVVM. Often they are implemented with the participation of additional libraries, e.g. Dagger (Dependency Injection), Otto (event bus), ButterKnife (bind view elements).
Architecture "classically"
In this approach, very often all the application logic is sewn into activity and fragment classes, which as a result become overloaded with responsibilities. There is a strong link between the view layer and the rest of the code which, in turn, makes it very difficult to make changes and further development of the application. Another negative effect is the inability to reuse code, as well as create tests. The whole application is difficult not only to maintain, but also to understand. In addition, due to the fact that very often we are dealing with asynchronous code, the application becomes even more complex (multiple nested callback functions).
In a slightly better variant, the whole application is divided into two layers:
- Model --- implements the logic of the application, e.g. access to the database, use of Rest API,
- View --- presents information, is responsible for interactions with the user.
Ideally, the model layer should be implemented using Service type classes, but in practice this is not often the case.
Model View Presenter (MVP) architecture.
MVP is a derivative of the Model View Controller pattern. The main difference between the two is in the way the components communicate with each other.
The entire application is divided into three layers:
- Model --- represents the problem domain and implements the business logic,
- Presenter --- operates at both the model and view level --- is responsible for executing the logic and configuring the view state,
- View --- passively presents data, passes information about the occurring events to the presenter.
It is worth noting that the presenter itself should be completely decoupled from the view technology. This approach ensures that unit tests can be performed at both the model and presenter levels without having to run the application on a simulator or physical device. In addition, if the view presenter class needs to be changed, for example, from activity to fragment, there is no need to modify the other layers.
Model View ViewModel (MVVM) architecture.
The View ViewModel is becoming increasingly popular not only on the Android platform. It is very often implemented using binning libraries that allow automatic synchronization of data with the view and vice versa (recently available natively on Android).
The entire application is divided into three layers:
- Model --- represents the problem domain and implements the business logic,
- ViewModel --- provides a model of data prepared for a specific view, implements the logic related to presentation,
- View --- defines the structure and distribution of view elements.
ViewModel classes should not contain code related to the view itself --- they are only responsible for providing a data model, such as a properly formatted date or user list. The main change from MVP is in the way it communicates --- the view watches the model and automatically refreshes itself when its state changes (two-way bindings are also possible). The benefits of this approach coincide with those listed for the MVP pattern, but thanks to bind, the developer does not have to write repetitive code that would be responsible for transferring state from the model to the view and vice versa.
When implementing MVVM, you can use any bind library, but it is worth mentioning the RxAndroid solution. This is an implementation of the Reactive Extensions library that allows you to create function-reactive style applications. You can say that it is a kind of extension of the observer pattern concept --- we observe sequences of events (mouse click, new data from the server, status change, etc.), and through special operator functions such streams can be modified, e.g. mapped, filtered or combined. In this way, everything that happens in the application is a consequence of the response to the event and there is no need to store the state directly. Very often this avoids a lot of logic associated with, for example, conditional presentation of information depending on the current state. It is worth adding that, unlike the observer pattern, the publisher can broadcast two additional types of events --- informing of an error or that the sequence has ended.
All the approaches discussed are demonstrated using a simple application displaying github repositories. They are available on my github (each architecture is a separate branch).