Note: This guide assumes that the reader has familiarity with the Android Framework. If you are new to app development, check out the Getting Started training series, which covers prerequisite topics for this guide.
Common problems faced by app developers
Unlike their traditional desktop counterparts which, in the majority of cases, have a single entry point from the launcher shortcut and run as a single monolithic process, Android apps have a much more complex structure. A typical Android app is constructed out of multiple app components, including activities, fragments, services, content providers and broadcast receivers.
Most of these app components are declared in the app manifest which is used by the Android OS to decide how to integrate your app into the overall user experience with their devices. While, as mentioned earlier, a desktop app is traditionally running as a monolithic process, a properly written Android app needs to be much more flexible as the user weaves their way through the different apps on their device, constantly switching flows and tasks.
For example, consider what happens when you share a photo in your favorite social network app. The app triggers a camera intent from which the Android OS launches a camera app to handle the request. At this point, the user leaves the social network app but their experience is seamless. The camera app, in turn, may trigger other intents, like launching the file chooser, which may launch another app. Eventually the user comes back to the social networking app and shares the photo. Also, the user could be interrupted by a phone call at any point in this process and come back to share the photo after finishing the phone call.
In Android, this app-hopping behavior is common, so your app must handle these flows correctly. Keep in mind that mobile devices are resource constrained, so at any time, the operating system may need to kill some apps to make room for new ones.
The point of all this is that your app components can be launched individually and out-of-order, and can be destroyed at anytime by the user or the system. Because app components are ephemeral and their lifecycle (when they are created and destroyed) are not under your control, you should not store any app data or state in your app components and your app components should not depend on each other.
Common architectural principles
If you can’t use app components to store app data and state, how should apps be structured?
The most important thing you should focus on is the separation of concerns in your app. It is a common mistake to write all your code in an Activity or a Fragment. Any code that does not handle a UI or operating system interaction should not be in these classes. Keeping them as lean as possible will allow you to avoid many lifecycle related problems. Don’t forget that you don’t own those classes, they are just glue classes that embody the contract between the OS and your app. The Android OS may destroy them at any time based on user interactions or other factors like low memory. It is best to minimize your dependency on them to provide a solid user experience.
The second important principle is that you should drive your UI from a model, preferably a persistent model. Persistence is ideal for two reasons: your users won’t lose data if OS destroys your app to free up resources and your app will continue to work even when a network connection is flaky or not connected. Models are components that are responsible for handling the data for the app. They are independent from the Views and app components in your app, hence they are isolated from the lifecycle issues of those components. Keeping UI code simple and free of app logic makes it easier to manage. Basing your app on model classes with well-defined responsibility of managing the data will make them testable and your app consistent.
Recommended app architecture
In this section, we demonstrate how to structure an app using Architecture Components by working through a use-case.
Note: It is impossible to have one way of writing apps that will be the best for every scenario. That being said, this recommended architecture should be a good starting point for most use cases. If you already have a good way of writing Android apps, you don’t need to change.
Imagine we’re building a UI that shows a user profile. This user profile will be fetched from our own private backend using a REST API.
Building the user interface
The UI will consist of a fragment UserProfileFragment.java and its corresponding layout file user_profile_layout.xml.
To drive the UI, our data model needs to hold two data elements.
The User ID: The identifier for the user. It is best to pass this information into the fragment using the fragment arguments. If the Android OS destroys your process, this information will be preserved so the id is available the next time your app is restarted.
The User object: A POJO that holds user data.
We will create a UserProfileViewModel based on the ViewModel class to keep this information.
A ViewModel provides the data for a specific UI component, such as a fragment or activity, and handles the communication with the business part of data handling, such as calling other components to load the data or forwarding user modifications. The ViewModel does not know about the View and is not affected by configuration changes such as recreating an activity due to rotation.