Jetpack Window Manager for foldable devices
Jetpack Window Manager provides a standard API for working with all foldable devices. It contains two important classes:
- DisplayFeature - Identifies disruptions in the continuous flat screen surface such as hinges or folds. Window Manager will return a collection of display features from a layout change callback.
- FoldingFeature - Provides information about a specific feature of the device. While the Surface Duo only has one folding feature, it's possible that other devices might have more.
A similar guide is in the Android Foldable Codelab. Read more about developing for foldables in the Android docs. Examples by the Android team are also available on GitHub. Jetpack release notes record changes in Window Manager as it is updated.
Tip
The controls and helper classes in the Surface Duo dual-screen library work with Window Manager. Follow the instructions to add the correct packages to your app project.
To use Window Manager directly in your code, follow the instructions below:
Add dependencies
Make sure you have the
mavenCentral()
repository in your top-level build.gradle file:allprojects { repositories { google() mavenCentral() } }
Ensure the
compileSdkVersion
andtargetSdkVersion
are set to API 31 or newer in your module-level build.gradle file:android { compileSdkVersion 31 defaultConfig { targetSdkVersion 31 } ... }
Add the following dependencies to your module-level build.gradle file:
Use Window Manager in your Kotlin code
When accessing Window Manager properties in your Kotlin projects, it's important to set up the correct flow of information. Otherwise, you may receive too few or too many event updates, and app performance could be affected.
To initialize and use a WindowInfoTracker
object, follow the steps below:
In your MainActivity class, create a variable for the
WindowInfoTracker
. Ensure thatimport androidx.window.layout.WindowInfoTracker
is added to the top of the file.class MainActivity : AppCompatActivity() { private lateinit var windowInfoTracker: WindowInfoTracker
Initialize the
WindowInfoTracker
in your activity'sonCreate
method and set up a flow to collect information from thewindowLayoutInfo
property.override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Initialize the window manager windowInfoTracker = WindowInfoTracker.getOrCreate(this@MainActivity) // Set up a flow lifecycleScope.launch(Dispatchers.Main) { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { windowInfoTracker.windowLayoutInfo(this@MainActivity) .collect { // Check FoldingFeature properties here } } } }
Ensure that these imports are also added to the top of the file:
import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch
Add code to check the
WindowLayoutInfo
flow for folding feature properties. When this code is run, the activity will update with the current device posture and display features (if spanned across a fold or a hinge).In the code snippet below, the activity displays different text based on the properties of a
FoldingFeature
.This example has a TextView called
layout_change_text
that shows the occlusion type andisSeparating
value for any detected folding features.override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) windowInfoTracker = WindowInfoTracker.getOrCreate(this@MainActivity) lifecycleScope.launch(Dispatchers.Main) { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { windowInfoTracker.windowLayoutInfo(this@MainActivity) .collect { newLayoutInfo -> layout_change_text.text = "No display features detected" for (displayFeature : DisplayFeature in newLayoutInfo.displayFeatures) { if (displayFeature is FoldingFeature && displayFeature.occlusionType == FoldingFeature.OcclusionType.NONE) { layout_change_text.text = "App is spanned across a fold, " + "isSeparating = ${displayFeature.isSeparating}" } if (displayFeature is FoldingFeature && displayFeature.occlusionType == FoldingFeature.OcclusionType.FULL) { layout_change_text.text = "App is spanned across a hinge, " + "isSeparating = ${displayFeature.isSeparating}" } } } } } }
Folding feature properties
The WindowLayoutInfo
class has a collection of DisplayFeature
items, one or more of which could be instances of the FoldingFeature class.
Folding features have the following properties:
bounds
- coordinates of the bounding rectangle of a folding featureocclusionType
- if a folding feature hides content (FULL
orNONE
)orientation
- orientation of a folding feature (HORIZONTAL
orVERTICAL
)state
- angle of a folding feature (HALF_OPENED
orFLAT
)isSeparating
- if a folding feature separates the display area into two distinct sections
You can query these properties to make decisions about how to adjust your layout after configuration changes.
isSeparating
When deciding where to place controls or how many panes of content to show, use the isSeparating property. This field will make sure your app provides the best user experience on all foldable devices:
- For dual-screen devices, this will always be true when an app is spanned across the hinge
- For other foldable devices, this will only be true when the state is
HALF_OPENED
, such as when a device is in the tabletop posture
Use the isSeparating
property to decide whether to adapt your app's UI layout for a foldable device, or use the default UI when there is no separation:
private fun updateCurrentLayout(newLayoutInfo: WindowLayoutInfo) {
for (displayFeature in newLayoutInfo.displayFeatures) {
val foldFeature = displayFeature as? FoldingFeature
foldFeature?.let {
if (it.isSeparating) {
// The content is separated by the FoldingFeature.
// Here is where you should adapt your UI.
} else {
// The content is not separated.
// Users can see and interact with your UI properly.
}
}
}
}
To see a more elaborate example of how to use this field, check out this isSeparating sample.
Google also provides documentation and samples related to this property as part of its large-screen and foldable guidance.
Samples
The surface-duo-jetpack-window-manager-samples GitHub repository contains a number of Kotlin samples demonstrating different dual-screen user experience patterns built using Jetpack Window Manager and the traditional view system.
The surface-duo-compose-samples GitHub repository also has dual-screen Kotlin samples that use Jetpack Window Manager, but in these samples the UI is built with Jetpack Compose.
Java API
Refer to the Jetpack Window Manager with Java blog post and this Java sample to see how to access the WindowInfoTracker
class via the WindowInfoTrackerCallbackAdapter
.