Architecture defines types of application components, their roles, and connections between them. It ensures clear separation of concerns and low coupling of elements resulting in their maintainability, scalability, and reusability. These benefits apply to both simple and complex systems, and they are technology agnostic. The decision which architecture to use is critical, and its subsequent changes are very expensive. For this reason, the developers responsible for the design should have a broad knowledge of the underlying technologies, tools, and standards.
Complexity of Android applications
Every Android developer knows that creating a mobile app is not a trivial task. There are plenty of aspects which you have to consider e.g. high fragmentation of devices, backward compatibility, API complexity, a variety of components, adequate management of resources, state synchronization, threading and others. Even simple application requires broad knowledge and a significant amount of work. Another aspect is app functional scope. At the beginning of the mobile era, applications were supposed to do one thing, e.g. showing the current weather. Today they are complex almost desktop like applications. These factors force the application of an architecture of some kind. It helps to conquer the complexity and give clear vision about how the app is working. Today, the growing number of Android developers realize how important this topic is. Although the official documentation does not give any particular solution, you can find examples on the Internet. There are many approaches, but three are the most common. Let’s call them classic, MVP and MVVM. Very often they are carried out with the help of external libraries like Dagger (Dependency Injection), Otto (Event bus), ButterKnife (view elements bindings).
In this approach, the whole business logic is placed in Activity and Fragment classes. As a result, they are heavy; they have too many responsibilities, and there are strong connections between the view layer and the rest of the code. It results in difficulties in maintaining, reusing and testing. The method of operation of the entire application is difficult to understand. Also, the code readability is very poor especially when you consider that a large part of it is asynchronous. In a modified, slightly better variant the application is divided into two layers:
- Model implements the application logic e.g. access to the database,
- View presents information, is responsible for interactions with the user.
Ideally, the model layer should be implemented using Service classes but in practice, this is often not the case.
Model View Presenter
MVP is derived from Model View Controller pattern. The main difference is how the components communicate. The application is divided into three layers:
- Model represents the problem domain and business logic,
- Presenter works both at the level of the model and the view, is responsible for the execution of logic and configures view state,
- View passively presents data, provide information about the events to the Presenter.
It is worth noting that the presenter should be completely independent from the view technology. This approach provides the ability to perform unit tests on the model and presenter without having to launch the application on the simulator or physical device. Additionally, if you want to change the presenting class e.g. from Activity to Fragment, it is not necessary to modify the remaining layers.
Model View ViewModel
MVVM is becoming increasingly popular not only on the Android platform. Very often it is carried out with the use of binding libraries, enabling automatic synchronization of the data model with the view, and vice versa (recently available natively on Android). The application is divided into three layers:
- Model represents the problem domain and business logic,
- ViewModel provides data model prepared for a specific view, performs the logic associated with the presentation,
- View defines the structure and layout of the view elements.
ViewModel classes should not contain code related to the view layer. They are solely responsible for providing the data model e.g. correctly formatted date or list of users. The main change compared to the MVP is in the way of communication — the view observes the model and when its state is changed it automatically refreshes (one can also use two-way binding). The benefits of such an approach coincide with those listed for the MVP pattern, but with bindings the programmer does not have to write repetitive code, which would be responsible for the transfer of state from the model to the view and vice versa.
When we talk about bindings, it is worth mentioning the library RxAndroid. It is an implementation Reactive Extensions which enables the creation of functional-reactive applications. You could say that it is a kind of extension of the Observer pattern — we observe sequences of events (a mouse click, the new data from the server, status change, etc.), and through individual operators, we can modify those sequences. For example, you can map, filter or combine them. As a result, everything that happens in the application is the result of the reaction to the events, and there is no need for direct state storage. Very often it allows avoiding a significant amount of logic associated with, e.g., conditional information presenting. It is worth noting that in contrast to Observer pattern publisher may advertise two additional types of events — indicating an error or that the sequence is over.
All of the discussed approaches were shown in the example of a simple application that displays github repositories. They are available at https://github.com/landrzejewski/android-architecture (each in different branch).