Blog Infos
Author
Published
Topics
, ,
Published

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 screens, how to add support for it in your app, and how to configure it and control aspects 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. Activityembedding was introduced in version 1.0.0-beta03, so make sure to use a version as recent as this one at minimum.
dependencies {
// Other app dependencies
implementation "androidx.window:window:1.0.0"
}
view raw build.gradle hosted with ❤ by GitHub

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.

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:window="http://schemas.android.com/apk/res-auto">
<SplitPairRule
window:splitRatio="0.4">
<SplitPairFilter
window:primaryActivityName=".list.ListActivity"
window:secondaryActivityName=".details.DetailsActivity" />
</SplitPairRule>
</resources>

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.

class ActivityEmbeddingSampleApplication : Application() {
override fun onCreate() {
super.onCreate()
SplitController.initialize(this, R.xml.split_configuration)
}
}

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.

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:window="http://schemas.android.com/apk/res-auto">
<!-- Other rules -->
<SplitPlaceholderRule
window:placeholderActivityName=".details.DetailsPlaceholderActivity"
window:splitRatio="0.4">
<ActivityFilter window:activityName=".list.ListActivity" />
</SplitPlaceholderRule>
</resources>

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 Activity, that is the Activity that was last touched or last launched. The system then finishes it. By default, finishing all Activitiesin 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.

Job Offers

Job Offers


    Delivery Lead / Scrum Master (m/w/d)

    Deutsche Post IT Services (Berlin) GmbH
    Berlin
    • Full Time
    apply now

    Android Entwicklerin Echtzeitkommunikation

    sipgate GmbH
    Germany
    • Full Time
    apply now

    Senior Android Developer (Remote)

    Komoot
    Europe
    • Full Time
    apply now
Load more listings

OUR VIDEO RECOMMENDATION

, ,

What does Recomposition mean to your app?

You’ve heard a lot that Jetpack Compose is a declarative UI toolkit and it recomposes only the components that changed. But what does it exactly mean? How does it apply not only in the scale…
Watch Video

What does Recomposition mean to your app?

Aida Issayeva
Senior Software Engineer
Android

What does Recomposition mean to your app?

Aida Issayeva
Senior Software Engi ...
Android

What does Recomposition mean to your app?

Aida Issayeva
Senior Software Engineer
Android

Jobs

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

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

finishSecondaryWithPrimary: When set to true, the system finishes Activitiesin 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.

<resources xmlns:window="http://schemas.android.com/apk/res-auto">
<SplitPairRule
window:finishPrimaryWithSecondary="true"
window:finishSecondaryWithPrimary="true"
window:splitRatio="0.4">
<SplitPairFilter
window:primaryActivityName=".list.ListActivity"
window:secondaryActivityName=".details.DetailsActivity" />
</SplitPairRule>
</resources>

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, ShareActivity, the 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.

<resources xmlns:window="http://schemas.android.com/apk/res-auto">
<SplitPairRule
<!-- split rule config option -->
>
<SplitPairFilter
window:primaryActivityName=".list.ListActivity"
window:secondaryActivityName=".details.DetailsActivity" />
<SplitPairFilter
window:primaryActivityName=".details.DetailsActivity"
window:secondaryActivityName=".share.ShareActivity" />
</SplitPairRule>
</resources>

The above configuration specifies that DetailsActivity should launch in the secondary container when it’s started by ListActivity, and should display in the primary container when it launches ShareActivity. The system handles animating 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 splitMinWidth, except 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().

class ListActivity : AppCompatActivity() {
private val splitListener = Consumer<List<SplitInfo>> { splitInfoList ->
// React to a split change
}
override fun onStart() {
super.onStart()
SplitController
.getInstance()
.addSplitListener(this, mainExecutor, splitListener)
}
override fun onStop() {
super.onStop()
SplitController
.getInstance()
.removeSplitListener(fabSplitListener)
}
}

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 Activityembedding.

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

val splitController = SplitController.getInstance()
if (!splitController.isSplitSupported()) {
// Split is not supported on this device
}
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 Activities, and 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.
  • Updating finishPrimaryWithSecondary and finishSecondaryWithPrimaryseemingly 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:

For more on Android, follow me to get notified when I write new posts, or let’s connect on Github and Twitter!

This article was originally published on proandroiddev.com on April 14, 2022

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog

How to animate BottomSheet content using Jetpack Compose

Early this year I started a new pet project for listening to random radio…
READ MORE
blog
Yes! You heard it right. We’ll try to understand the complete OTP (one time…
READ MORE

Leave a Reply

Your email address will not be published.

Fill out this field
Fill out this field
Please enter a valid email address.

Menu