Over the years, I’ve seen many different navigation pattern implementations in Android. Some of the apps were using only Activities, while others Activities mixed with Fragments and/or with Custom Views.
One of my favorite navigation pattern implementations is based on the “One-Activity-Multiple-Fragments” philosophy, or simply the Fragment Navigation Pattern, where every screen in the application is a full screen Fragment and all or most of these fragments are contained in one Activity.
This approach not only simplifies how the navigation is implemented, but it has much better performance and consequently offers a better user experience.
In this article we will look at some common navigation pattern implementations in Android, and then introduce the Fragment based navigation pattern, comparing and contrasting with the others. A demo application implementing this pattern has been uploaded to GitHub.
World of Activities
A typical Android application which uses only activities is organized into a tree-like structure (more precisely into a directed graph) where the root activity is started by the launcher. As you navigate in the application there is an activity back stack maintained by the OS.
A simple example is shown in the diagram below:
Activity A1 is the entry point in our application (for example, it represents a splash screen or a main menu) and from it the user can navigate to A2 or A3. When you need to communicate between activities you can use the startActivityForResult() or maybe you share a globally accessible business logic object between them.
When you need to add a new Activity you need to perform the following steps:
- Define the new activity
- Register it in the AndroidManifest.xml
- Open it with a startActivity() from another activity
Of course this navigation diagram is a fairly a simplistic approach. It can become very complex when you need to manipulate the back stack or when you have to reuse the same activity multiple times, for example when you would like to navigate the user through some tutorial screens but each screen in fact uses the same activity as a base.
Fortunately we have tools for it called tasks and some guidelines for proper back stack navigation.
Then, with API level 11 came fragments…
World of Fragments
“Android introduced fragments in Android 3.0 (API level 11), primarily to support more dynamic and flexible UI designs on large screens, such as tablets. Because a tablet’s screen is much larger than that of a handset, there’s more room to combine and interchange UI components. Fragments allow such designs without the need for you to manage complex changes to the view hierarchy. By dividing the layout of an activity into fragments, you become able to modify the activity’s appearance at runtime and preserve those changes in a back stack that’s managed by the activity.” – cited from the Google’s API guide for Fragments.
This new toy allowed developers to build a multi-pane UI and reuse the components in other activities. Some developers love this while others don’t. It is a popular debate whether to use fragments or not, but I think everybody would agree that fragments brought in additional complexity and the developers really need to understand them in order to use them properly.
Fullscreen Fragment Nightmare in Android
I started to see more and more examples where the fragments were not just representing a part of the screen, but in fact the whole screen was a fragment contained in an activity. Once I even saw a design where every activity had exactly one full screen fragment and nothing more and the only reason why these activities existed was to host these fragments. Next to the obvious design flaw, there is another problem with this approach. Have a look at the diagram from below:
How can A1 communicate with F1? Well A1 has total control over F1 since it created F1. A1 can pass a bundle, for example, on the creation of F1 or can invoke its public methods. How can F1 communicate with A1? Well this is more complicated, but it can be resolved with a callback/observer pattern where the A1 subscribes to F1 and F1 notifies A1.
But how can A1 and A2 communicate with each other? This has been covered already, for example via startActivityForResult().
And now the real question comes: how can F1 and F2 communicate with each other? Even in this case we can have a business logic component which is globally available, so it can be used to pass data. But this does not always lead to elegant design. What if F2 needs to pass some data to F1 in a more direct way? Well, with a callback pattern F2 can notify A2, then A2 finishes with a result and this result is captured by A1 which notifies F1.
This approach needs a lot of boilerplate code and quickly becomes a source of bugs, pain and anger.
What if we could get rid all of the activities and keep only one of them which keeps the rest of the fragments?
Fragment Navigation Pattern
Over the years I started to use the “One-Activity-Multiple-Fragments” pattern in most of my applications and I still use it. There are a lot of discussions out there about this approach, for example here and here. What I missed however is a concrete example which I can see and test myself.
Let’s have a look at the following diagram:
Now we have only one container activity and we have multiple fragments which have again a tree like structure. The navigation between them is handled by the FragmentManager, it has its back stack.
Notice that now we don’t have the startActivityForResult() but we can implement a callback/observer pattern. Let’s see some pros and cons of this approach:
Pros:
1. Cleaner and more maintainable AndroidManifest.xml
Now that we have only one Activity, we no longer need to update the manifest every time we add a new screen. Unlike activities, we do not have to declare fragments.
This could seem like a minor thing, but for larger applications which have 50+ activities this can significantly improve readability of the AndroidManifest.xml file.
Look at the manifest file of the example application which has several screens. The manifest file still remains super simple.
<?xml version="1.0" encoding="utf-8"?>
package="com.exarlabs.android.fragmentnavigationdemo.ui" >
<application android:name= ".FragmentNavigationDemoApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name="com.exarlabs.android.fragmentnavigationdemo.ui.MainActivity"
android:label="@string/app_name"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>