Activity Embedding preview for large screens and foldables
Hello Android developers,
The Jetpack Window Manager (JWM) library helps you build adaptive UIs that work well on any device form factor (i.e. single screen, dual screen, foldable, and large screen devices). Jetpack Window Manager provides you with the information that you need to adapt your app’s UI, so it works well on any device. For example, you can know where a folding feature is located, the posture that the device is using according to the folding feature and its orientation, etc.
Activity Embedding is part of Jetpack Window Manager version 1.1 alpha and contains APIs that help you to optimize apps on large screen devices by splitting an application’s task window between two activities or two instances of the same activity.
Figure 1. Settings app with activities side by side. (source)
Activity Embedding helps with legacy codebases that are based on Activities (e.g., every screen is an Activity, or it has several Activities with Fragments inside of them, etc.) that want to get enhanced and build adaptive UIs that work well on different form factor devices. With Activity Embedding, we can define specific rules that define how we want to show the activities in our app when satisfying them.
For example, we could define a rule that whenever there is a specific enough width available, we show two activities side-by-side, or otherwise, stacked one on top of the other:
Figure 2. Activity Embedding will show one activity by default and will show two activities side-by-side when the rules match.
If your codebase is based on single-activity architecture, you may take a look to other components built for these use cases such as SlidingPaneLayout or the Jetpack Compose components we have created.
Activity Embedding is an experimental API that may change. It’s available for devices that run Android 12L (API v32) or higher.
There are many more features and aspects you should consider that we don’t cover here and that are explained in the official docs.
To use Activity Embedding, you will have to add Jetpack Window Manager to your build.gradle file. Most recent update is
Activity Embedding usage is based on an XML configuration file and Window Manager API calls. You will have to place an xml file in the xml folder with the split rules you want to use and where you define which activities should be split.
Activity Embedding sample
This sample app helps explain how to take use Activity Embedding for a scenario where multiple Activities are used in an app, and where we want to build an adaptive UI that works well on foldable and large-screen devices.
The sample app is based on four activities which starts with Activity A (as defined in the manifest), and then each activity opens the following one, in this order: A -> B -> C -> D.
If you open the app on Surface Duo 2 in single screen mode, you will see each activity occupy the entire screen as you navigate from one to the next. The activities are stacked one on top of the other.
In dual-screen mode on Surface Duo 2, or on a large screen device (or any device whose window size meets the rules we are going to use), it will show activities side-by-side according to the rules we have set.
The sample highlights these three features:
- Adding an activity as placeholder in a two-pane scenario. This is useful when you need to show two activities side-by-side (e.g., a list with options on the left and the content at the right), but the content is not ready, to help handle that, we can show an activity as a placeholder.
- Show two activities side-by-side according to a split rule. This split action will be explicitly triggered from the source activity.
- Detecting whether an activity is running embedded or not.
Let’s look at them one by one, but first is important to mention that we have to inform the library about the rule definitions we have. For that, we are going to use the Jetpack Startup library to perform an initialization before other components of the app load and activity start. We have to add an entry in the manifest were we setup the startup library and link to our Activity Embedding initializer where we indicate the rules file xml that we are going to use. In our case, the one located at /res/xml/main_split_config.xml.
Now, let’s see finally the three different features we want to cover:
Show two Activities side-by-side defining a split placeholder rule
As mentioned in the docs, sometimes is useful to have a default state, so when we need to open two activities side-by-side but the activity with the content is not ready we can show a placeholder. How can we define and use one?
As you can see in the app /res/xml/main_split_config.xml:
<SplitPlaceholderRule window:placeholderActivityName="com.cesavaliente.embedding.ActivityB" window:splitMinWidth="537dp" window:splitRatio="0.5"> <ActivityFilter window:activityName="com.cesavaliente.embedding.ActivityA" /> </SplitPlaceholderRule>
Figure 3. SplitPlaceholderRule with the ActivityFilter where we define with activity will trigger this split and the placeholderActivityName where we define the activity that will act as placeholder.
Here, inside SplitPlaceholderRule we are saying that when we have at least 537dp width window size (splitMinWidth), and we are in the Activity A (ActivityFilter) then we will show the placeholder (Activity B) side-by-side with the Activity A in a 0.5 screen splitRatio. If we don’t satisfy the rule, then we will just show activity A.
If you are wondering about why we are using that specific value (537dp) in splitMinWidth, that’s because in this sample, we want to have the side-by-side activity scenario just when the app is spanned across displays on Surface Duo 2, and each display on this device, has a 537dp width.
For splitRatio set to 0.5, that’s because we want that each activity, when running them side-by-side, take a whole display, so each one takes exactly 50% of the whole display area available.
Figure 3. Activity A and B are shown in single screen mode (on top of the other) and later as placeholder in dual screen mode (side-by-side).
Is important to mention that Activity Embedding is not fold aware, that means that depending on the rules we define, the content may be or not separated and/or occluded by a FoldingFeature. Since the library doesn’t handle FoldingFeature automatically, placing the content around it, we must do it by ourselves, hence the importance of defining attributes that work for most devices or scenarios (or creating different rules).
Show two Activities side-by-side defining a split pair rule
We can define as well that whenever we launch a specific activity, if the split-rules are satisfied, the activity can be launched side-by-side with its parent.
If we go to the xml file where we have the rules defined, we can see now:
<SplitPairRule window:finishPrimaryWithSecondary="adjacent" window:finishSecondaryWithPrimary="adjacent" window:splitLayoutDirection="rtl" window:splitMinWidth="537dp" window:splitRatio="0.5"> <SplitPairFilter window:primaryActivityName="com.cesavaliente.embedding.ActivityC" window:secondaryActivityName="com.cesavaliente.embedding.ActivityD" /> </SplitPairRule>
Figure 4. SplitPairRule and the rule to show two activities side-by-side if the rule is satisfied.
In this node, SplitPairRule, the most important part is the SplitPairFilter node, here we are defining which one will be the primary activity (or parent) and which one the secondary activity (or child).
Then with attributes such as what we saw before splitMinWidth and splitRatio we can define the window configuration we have to have to show them side-by-side. Otherwise, they will be launched one on top of the other (as usual).
Figure 5. All activities are shown on top of each other in single screen mode and activity D is launched side-by-side later (from activity C) when in dual-screen mode (when split rule is satisfied).
Check whether an activity is embedded or not
The last thing we want to show you is to check if an activity that is being shown embedded or not. To know that we must use this API through code; using the SplitController instance and call the specific isActivityEmbedded function:
val isActivityEmbedded = SplitController.getInstance().isActivityEmbedded(this)
Figure 6. Knowing whether an activity is embedded or not using the SplitController instance.
In the below figure, we can see the result of calling this function on both scenarios. We can see how a toast shows that the activity is not embedded (false) when running the app in single screen mode, and that the activity is embedded (true) when running in dual-screen mode with two activities side-by-side:
Figure 7. A toast shows that the activity D is not embedded in single screen mode (left) and that it is, in dual-screen mode (right).
Note: the above figure has been created using a Pixel C emulator. If you want to reproduce the same scenario in the sample app, you will have to change splitMinWidth to 1100dp and splitRatio to 0.2 in both rules (SplitPlaceHolder and SplitPairRule) in the xml rules file.
Resources and feedback
You can also live chat with us on Twitch this Friday at 13h CET and later at 11am PST!