Activity Embedding in Android. Multi-pane support with Activity… | by Husayn Hakeem | Apr, 2022

Google released Android 12L to improve the Android feel and experience on foldables, tablets, Chrome OS devices and in general, large screens. This release comes at a time when large screens are growing in reach, thus increasing the need to scale app UI to support a wide range of screen sizes.

Android 12L introduced many improvements, from multitasking and split-screen features to UI optimization and polishing. It was also accompanied by developer tools and APIs to make building, designing and testing on large screens easier.

This post goes over Activity embedding, an addition to Android 12L that brings support for multi-pane layouts in Activity based apps. The post goes over how Activity embedding impacts app behavior on both small and large aspects of screens, how to add support for it in your app, and how to configure it and control such as Activity launch and back navigation.

The code samples used in this post can be found here.

Introducing Activity Embedding

Activity embedding was recently introduced in Jetpack WindowManager. It’s available for large screen devices with Android 12L. It brings support for multi-pane layouts to Activity based apps, allowing developers to provide an improved user experience on large screens without significant refactoring/migration to Fragments or Jetpack Compose.

Activity embedding splits the task window into 2 containers, a primary container and a secondary one. On large screens, primary and secondary Activities are laid out side by side, whereas on small screens, secondary activities are stacked on top of primary ones. The system handles this behavior depending on the configuration the app defines.

Left: Primary and secondary Activities side by side on a large screen. Right: Secondary Activities stacked on top of primary Activities on a small screen.

Use Case: List/Details Pattern

The list/details pattern is commonly used in apps. On small screens, the list and details screens are each shown on their own. On large screens though, showing the list and details screens side by side can make better use of the available real estate on the screen and provide a better user experience.

Let’s assume we have an app where the list and details screens are defined in ListActivity and DetailsActivity respectively. This is how they would look on small and large screens.

Left: List/Details screens in a large screen. Right: List/Details screens in a small screen.

List/Details with Activity Embedding

Adding support for Activity embedding in this app can be achieved in 3 simple steps.

  1. Add the WindowManager dependency to the app’s build.gradle file. Activity embedding was introduced in version 1.0.0-beta03so make sure to use a version as recent as this one at minimum.

2. Create a configuration file in the folder res/xml. In it, you’ll provide rules to define how and which Activities should be split. The system will later use them to decide how to handle embedding them. The following is the minimum required configuration.

3. Initialize SplitController. This has to be done before the app loads and starts its Activities. One option is to perform this initialization on app launch inside the Application class, another is to use the Jetpack Startup library.

ListActivity and DetailsActivity will now be displayed side by side on large screens. On small screens though, the app will continue to behave the same, that is, when DetailsActivity is launched, it is stacked on top of ListActivity.

Left: List/Details screens side by side in a large screen. Right: List/Details screens stacked in a small screen.

Adding a Placeholder

Activity embedding provides a way to display a placeholder Activity in the secondary container until there’s content to show in it. This is useful in the list/details use case for when the user hasn’t selected an item from the list yet.

The placeholder Activity is only shown when there is enough space for a split. It stays displayed until there is content to show, in which case it is dismissed.

Setting a placeholder Activity DetailsPlaceholderActivity as a placeholder until a list item is selected is quite simple. Just define a rule for it in the split configuration file.

This results in a placeholder Activity being displayed initially on large screens before an item from the list has been selected.

The list and placeholder screens side by side

Back Navigation

When navigating back using gesture navigation or the back button, the back event is sent to the focused Activitythat is the Activity that was last touched or last launched. The system then finishes it. By default, finishing all Activities in one container results in the other container filling the entire space of the screen.

Finishing the secondary container’s Activities results in the primary container expanding to fill the whole screen.
Finishing the primary container’s activities results in the secondary container expanding to fill the whole screen.

Activity embedding provides 2 options to control how Activities in the primary and secondary container are finished.

finishPrimaryWithSecondary: When set to truethe system finishes the Activity in the primary container when all Activities in the secondary container are finished. By default it’s set to false.

finishSecondaryWithPrimary: When set to truethe system finishes Activities in the secondary container when all Activities in the primary container are finished. By default it’s set to true.

Going back to our example, we’d like ListActivity to finish when DetailsActivity finishes, and vice versa, DetailsActivity should finish when ListActivity finishes. This is achieved using the following configuration.

Testing this code on a large screen doesn’t result in the expected behavior though. This might be due to a bug in the library.

Multiple Activities

DetailsActivity is displayed in the secondary container. If it were to launch another Activity, ShareActivitythe latter would be stacked at the top of this container.

ShareActivity stacked on top of DetailsActivity when it is launched

Besides this default launch behavior, Activity embedding provides the option to shift splits sideways when launching an Activity from the secondary container. That means that DetailsActivity would move to the primary container, and ShareActivity would be launched in the secondary container.

DetailsActivity launches ShareActivity to the side and shifts the split

To support this behavior, you need to define the corresponding split rule for DetailsActivity and ShareActivity.

The above configuration specifies that DetailsActivity should launch in the secondary container when it’s started by ListActivityand should display in the primary container when it launches ShareActivity. The system handles animation DetailsActivity as it moves between containers.

Split shift in action

Split Rule Configuration Options

Activity embedding provides a couple of options to configure split rules:

splitRatio: A float that defines the ratio of the primary container to the secondary container. By default it’s set to 0.5, meaning that each container occupies half the available screen width. It appears that this parameter is mandatory, failing to set it results in splits not working.

splitMinWidth: A dimension that defines the minimum window width to trigger a split. If the window width is smaller than this value, Activities in the secondary container will be stacked on top of Activities in the primary one. By default, it’s set to 600dp.

splitMinSmallestWidth: similar to splitMinWidthexcept that it takes into account both the window’s width and height.

clearTop: A boolean that defines whether to clear all Activities in the secondary container when launching an Activity in a split with the same primary container. By default, it’s set to false.

Listening to Split Events

Activity embedding provides an API to receive notifications about split changes, allowing the app to react to them.

A split listener is registered using SplitController.addSplitListener(). This method takes an Activity as an argument, which ensures the listener only receives updates about active splits the Activity is part of. It also accepts an Executor on which updates are received.

A split listener should be unregistered using SplitController.removeSplitListener().

This split listener is a Consumer that receives a list of SplitInfo on each update, each including information about Activities in the primary and secondary containers, as well as the split ratio.

A common scenario for using a split listener is to update the UI. An example is showing or hiding a FloatingActionButton (FAB) depending on the state of the split. Using our list/details app, imagine the FAB is part of ListActivity. On a large screen where ListActivity and DetailsActivity are displayed side by side, ListActivity should hide the FAB, whereas DetailsActivity should show it. On small screens though, ListActivity should be the one to show the FAB.

You can find the code for this example here in the code sample.

Using a split listener to show a FAB in DetailsActivity on the initial screen, and hide it when it launches ShareActivity.

Split Support

Activity embedding is supported on large screen devices running API levels 32 or higher. Some devices with earlier versions may support it though, this depends on the OEM and whether they retroactively add support for it.

When running on Android 12L, large screen emulators and the resizable emulator included in Android Studio Chipmunk also support Activity embedding.

You can check if a device supports Activity embedding or not at runtime as follows.

Conclusion

In summary:

  • Use Activity embedding in your Activity based app to support multi-pane layouts, allowing your users to see/do/experience more on large screens by making use of the extra screen real estate.
  • Provide a split configuration file that defines the split rules the system uses to handle embedding Activities in your app.
  • Define how embedded Activities should launch other Activitiesand control how the system finishes them.
  • Customize split rules by using the supported options which include splitRatio, minSplitWidth and clearTop.
  • Register a split listener to receive updates on active splits. Don’t forget to unregister it!
  • Check for split support on devices at runtime using SplitController.isSplitSupported().

Notes worth mentioning from testing:

  • Activity embedding only works if the split rule defines splitRatio.
  • Update finishPrimaryWithSecondary and finishSecondaryWithPrimary seemingly has no effect on the finishing behavior of Activities in the primary and secondary containers. (Potential bug)
  • If Activities that support embedding are run in a task that belongs to another app, then embedding will not work for them.

Want to learn more about Activity embedding on Android? Check out:

Leave a Comment