Re-Architecting the ownCloud App for Android
The Android team is currently rewriting the ownCloud app to change the architecture. The first improvements will be released soon. What can you learn from this?
Renew or Die
8 years ago, in August 2011, the ownCloud Android repository received its first commit. It marked a turning point in the way of uploading files to a private cloud and sharing them with others, everything from the palm of our hands.
As it happens to all software in the world, the ownCloud app for Android was getting older. Catching up with the latest Android features was being increasingly more difficult. Besides, there were many parts of the code needing a refactoring.
Meanwhile, Android was getting more mature, modern and Kotlin was set as official language for Android development. Additionally, the Android team introduced the Android architecture components a couple of years ago, a collection of libraries to develop robust, testable and more maintainable apps.
So around a year ago, we finally gave a step forward and started to design what it would finally turn into a new architecture for the ownCloud app for Android.
Moving to a New Architecture
Before moving every kind of software to a new architecture, you should define a strategy to follow. In our case, we chose a simple use case like getting shares from server and showing them in a list and started to transform it from the old architecture to a new one – I will use this use case to explain everything related to new architecture in this blogpost.
From MVC to MVVM
Let’s introduce these two design patterns that belongs to the presentation layer and how they affect the ownCloud app for Android.
What Is the MVC Pattern?
The MVC architecture pattern (model – view – controller) is what we have traditionally been using in the ownCloud app for Android and consists of:
- Model: contains the information which the system works with and provides it to the view so it can be displayed. Besides, allows applying changes in the view from the controller. An example of model in our app is OCShare, which contains all the information needed to work with a share such as name, file path, user to share with, permissions and so on.
- Controller: responds to user actions, modifying the model when needed. It also communicates with the view to update it with the latest changes in the model. A controller in our app is ShareActivity.
- View: it presents model information to the user. Some views in our app are ShareActivity and ShareFragment.
One of the most common mistakes when implementing MVC in Android has always been using an Activity as a controller, making it responsible of tasks that should not take care of, like calling directly the model to modify data, when this should be a controller task. This is clearly a violation of the single responsibility principle and when using an Activity as a controller we are tying it to the Android platform so its code could be affected if the Activity is destroyed by the system.
If we want to properly use MVC in Android, activities and fragments should only take care of showing data and notifying user events to a controller, being the only components linked to the Android platform. Hence, controllers and models should be separate classes without any Android dependency so that can be easier to test. But this is not the case of the ownCloud app for Android since there were some activities with a controller role, assuming responsibilities that should not involve them.
And the MVVM Pattern?
On the other hand, MVVM architecture pattern is what we are going to use in the ownCloud app for Android from now on. It consists of:
- Model: as in MVC, it represents data and business logic. The model in the ownCloud app for Android would keep being OCShare.
- View: shows information and is active, reacting to model changes, similar to an active MVC pattern. ShareActivity, ShareFragment are the views but with no controller responsibilities.
- View model: is the intermediary between model and view and contains presentation logic. There was no existing classes of this type, so we have created OCShareViewModel.
Now that we have introduced these two patterns, why have we decided to use MVVM in favor of MVC in the app?
- In MVVM, the view will depend only on the viewmodel to get data and modify it and will directly observe changes in the model using databinding. Therefore, we will decouple code from the view.
- The model in MVC usually has many responsibilities such as obtaining data from data sources, informing the controller about changes on that data and prepare them to be displayed in the view. In MVVM, the model is totally decoupled from the view and only contains information, never actions to manipulate it.
- In MVVM, the presentation logic is handled by the viewmodel, meanwhile in MVC this responsibility is barely clear.
So with MVVM we are basically distributing responsibilities in a better way to reduce dependencies and make the code easier to test and debug.
Android Architecture Components
These components released by the Android team a couple of years ago make our life easier when it comes to implementing a MVVM pattern in Android, as well as achieving more robust, maintainable and easier to test apps. They consist of:
- LiveData: objects that notify the view when there is any change in database and are lifecycle aware so it can help us to avoid crashes when an activity is stopped. In the app we use a list of shares as livedata.
- ViewModel: responsible for preparing and handling Activity or Fragment data. It exposes the information through a LiveData observed from the view. Regarding getting shares usecase, ShareFileFragment observes changes in shares livedata.
- Room: ORM (Object-Relational mapping) library which converts SQLite to Java/Kotlin objects automatically. It allows SQL validation in compile time and returns LiveData objects to observe changes in database.
You can see below a diagram representing the getting shares use case implemented with MVVM and Android architecture components.
So you can have a look at the diagram above and see the flow to get shares and show them in the view:
- ShareFileFragment observes the list of shares stored in a livedata object exposed by OCShareViewModel.
- OCShareViewModel obtains the shares from the OCShareRepository without knowing where they come from, is a completely transparent transaction, thanks to repository pattern.
- OCShareRepository obtains the shares from OCRemoteSharesDataSource, which uses the ownCloud Android Library to fetch the shares from server. After that, it updates the database with the new shares by using OCLocalSharesDataSource.
- As soon as new shares are available in database, ShareFileFragment is automatically notified through the observer and shows the shares to the user.
In the diagram you will also have noticed some classes with a light blue background such as XMLLocalSharesDataSource or FirebaseRemoteSharesDataSource, which represent the extensibility of this solution that allows changing the data sources in the future and getting the shares from XML, JSON files, different APIs and so on.
Modularizing the App
MVVM and also MVC are design patterns used in the presentation layer, but an application is more than just one layer. To represent the different layers it is normal to use several modules, one for each layer.
You might be wondering what is a module here. A module is a component of an Android app that we can build, test and debug independently.
Originally, the ownCloud Android project consisted of two modules, the app itself and the library but from now on we will have four layers divided in four different modules, as you can see in the picture below.
What are the advantages of doing this modularization?
- Development scalability: several developers working in different modules independently.
- Maintainability: gradle can build the modules separately, speeding up the build process and CI.
- Less tests to run when there’s a new change: we need to run the tests in the module affected by the new change and anything else that depends on it. We do not need to run all the tests if other modules are not impacted by the changes.
- Less coupled code.
- Module switching: if in the future we want an app with a different UI and a different way to handle the information to show, we would just need to replace the :ownCloudApp module. And the same for the rest of modules, if one day we want to handle data differently to the current implementation, replacing the :ownCloudData module should be enough.
What Have We Already Achieved?
We have totally rewritten the sharing feature in Kotlin, following the architecture detailed above.
As you can see in the graphs below, we have also increased the use of Kotlin in the app if we compare it with previous versions, from 1.1% to almost 30%, without taking into account the xml resources.
This is a huge achievement because Kotlin code is easier to maintain and to read than Java. It is also safer, helping us to reduce the amount of crashes in the app for instance. In addition, it makes it easier for the community to contribute.
Tests are an important part in software development so the number of both unit and UI tests has also been increased, reaching 224 new tests.
Using a proper programming language and increasing the number of tests is decisive; but users do not usually notice this sort of changes in the app. What the users can see, among others, is the memory used by the app and how fast it reacts.
The diagrams below shows the memory used by the app when using the share view. It is 5 MB lower than when the sharing was using the old architecture:
We have also measured how long it takes to open the share view after pressing the share icon of a file. The time has reduced as you can see below:
We expect that these performance improvements will also happen in the other parts of the app which will be part of the rewrite.
The next topic to address within the new architecture is the modularization of the app, as described above. To complete this modularization we would need to decouple some objects related to authorization such as the ownCloud clients and accounts.
This process will come hand in hand with a complete refactorization of the login. Afterwards, files and synchronization will be the next challenge.
Do you want to try out our current improvements? The beta is already out! Get it at the playstore:
What do you think about this progress? Leave a comment below or share this article on social media!