Reasons to use Android Single-Activity Architecture with Navigation Component

Instead of having one Activity represent one screen, we view an Activity as a big container with the fragments inside the Activity representing the screen.

Every Android developer is familiar with an Activity. The Activity might even be the first thing you use in the Android development world. The number of Activities in our application usually depends on the complexity of the design.

Since the announcement of Jetpack in Google I/O 2018, Single Activity Architecture is also mentioned and it seems like the Google team intended to make this architecture more preferable.

Today we are introducing the Navigation component as a framework for structuring your in-app UI, with a focus on making a single-Activity app the preferred architecture.

from https://android-developers.googleblog.com/2018/05/use-android-jetpack-to-accelerate-your.html?m=1

Briefly, the Single-Activity Architecture is the architecture that has only one Activity or a relatively small number of Activities. Instead of having one Activity represent one screen, we view an Activity as a big container with the fragments inside the Activity representing the screen.

But why might we want to use a Single Activity Architecture? What are the problems with previously used architectures?

1. Have you ever create Activity with only one Fragment inside just because people told you that it is the best practice? 


In my opinion, using a Fragment inside an Activity is indeed a best practice, and we can reuse that Fragment in other Activities as well. However, if we work on the UI that is only used in a single place, do we still need to create an Activity with one Fragment inside, or should we create an Activity without any Fragment? With the Single Activity Architecture, we can remove those concerns because our application will only have one Activity.

2.  There has always been a problem with transition animation between Activities.


Take a look at the status bar when the transition between Activities occurs. It blinks during the transition.



The solution to this issue is not that straight-forwarded. There are some gotcha snippets you need to add. 
 
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_flash_fix_a);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Fade fade = new Fade();
            fade.excludeTarget(R.id.appBar, true);
            fade.excludeTarget(android.R.id.statusBarBackground, true);
            fade.excludeTarget(android.R.id.navigationBarBackground, true);

            getWindow().setEnterTransition(fade);
            getWindow().setExitTransition(fade);
        }

        //Button setup
    }

There is nothing written about this in the official documentation, and yet you really need to add this to make the transition look smooth. (You can read more about it here (https://mikescamell.com/shared-element-transitions-part-5/)

However, this issue does not occur in the transition between Fragments.

3. Sharing data between Activities


Sharing data between activities.


Most of the time when we want to share data between Activities, we put the data into a Singleton Data Holder like Application Class. The problem is that data is in the Application Scope which is the scope that Service and Content Provider can also access. We intend to share the data only between Activities, not the other elements.

Then how can Single Activity Architecture help us solve this problem? Let's take a look at the below picture.

Sharing data in the Single Activity Architecture

If we use Single Activity Architecture, the sharing of the data happens in the Fragments level and all of the Fragments are wrapped inside the Activity. Doing this keeps the shared data away from other elements in the Application Scope which means it cannot be accessed by Service or Content Provider.

4. Passing arguments into the Fragment can be painful sometimes.


Imagine the situation where you need to pass information into the Fragment and some of them are nullable while some are not. You might end up with the code look similar to this:

fun newInstance(
        obj: Object1? = null,
        id: Int,
        minimumPrice: Int? = null,
        promotionId: Int? = null,
        selectionId: Int? = null,
        itemPosition: Int? = null
): SomeFragment {
    val fragment = PizzaOptionFragment()
    fragment.arguments = Bundle().apply {
        putParcelable(Constants.Intent.Obj1, obj)
        putInt(Constants.Intent.ID, id)
        minimumPromotionPrice?.let { putInt(Constants.Intent.Promotion.MINIMUM_PRICE, it)}
       promotionId?.let { putInt(Constants.Intent.Promotion.PROMOTION_ID, it) }
        selectionId?.let { putInt(Constants.Intent.Promotion.SELECTION_ID, it) }
        itemPosition?.let { putInt(Constants.Intent.Promotion.ITEM_POSITION, it) }
    }
    return fragment
}

With the Navigation component, there is a feature called Safe Args Gradle Plugin. 

The plugin that generates simple object and builder classes for type-safe access to arguments specified for destinations and actions.

Here are 3 simple steps to pass an argument into a Fragment with Safe Args Gradle Plugin. Suppose we want to pass an argument URL from BerryFragment to BerryDetailFragment

-  Define argument in destination Fragment in Navigation Graph.

Picture of the navigation graph editor
- Passing data (the snippet is inside BerryFragment) If there is more than one argument, they will be added as function parameters.

val direction = BerryListFragmentDirections
                   .actionBerryListFragmentToBerryDetailFragment(item.url)

navController.navigate(direction)

- Retrieving data  (the snippet is inside BerryDetailFragment)

val args: BerryDetailFragmentArgs = arguments?.let{
    BerryDetailFragmentArgs.fromBundle(it)
 }

val berryUrl: String = args.url

5. Easy deep linking
The navigation component library allows us to directly define the deep link schema right where the Fragment that we want to land on is. The deep link URL can be defined within two simple steps.

-  Define deep link URL in the Navigation Graph. 
picture of the navigation graph editor


- Define the navigation graph in the Manifest file.

<manifest …>

    <uses-permission android:name="android.permission.INTERNET"/>
    <application …>

        <activity …>

            <nav-graph android:value=“@navigation/main_navigation"/>

            <intent-filter>
               <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>

        </activity>

    </application>
</manifest>


The defined schema can also be used for navigating within the app by using NavController.

Conclusion


Apart from the mentioned things above Navigation Component library also come with an additional library called Navigation UI Libs. It helps managing navigation with the top app bar, the navigation drawer, and bottom navigation.

Are there any problems using the Single-Activity Architecture with the Navigation component?

We have used this architecture in several production apps and so far there are no issues. You might wonder what if we want to pass the data back and forth between Fragments like startActivityForResult? Previously, we used a shared ViewModel for communication between fragments. However, recently, Google has just added a new ability to FragmentManager which allowed the FragmentManager to act as a central store for fragment results. We can pass the data back and forth between Fragments easily. You can read more about it here https://developer.android.com/training/basics/fragments/pass-data-between#kotlin

If you are about to start the new app, I think it worth a try using Single-Activity Architecture with the Navigation component. However, in the case where you want to use it with the existing app with many Activities, you can start off by transforming the flow to use this architecture. For example, in the authentication flow, instead of having multiple Activity for Login, Sign up, etc, you can combine that into one Activity with Fragment representing each screen in the flow.

Looking for a new challenge? Join Our Team

Like 5 likes
Ben Kitpitak
Mobile Developer at OOZOU in Bangkok, Thailand
Share:

Join the conversation

This will be shown public
All comments are moderated

Get our stories delivered

From us to your inbox weekly.