Original address: medium.com/androiddeve…
The author is medium.com/@pmaggi
Published: August 20, 2021-6 minutes to read
Optimize applications for foldable and large-screen devices
Android screen sizes are changing rapidly, and with the growing popularity of tablets and foldable devices, knowing your app’s window size and state is critical to developing a responsive UI. Jetpack WindowManager, currently in beta, is a library and API that provides functionality similar to Android Framework WindowManager, including support for responsive UI, callback adapters to detect screen changes, and window testing apis. But Jetpack WindowManager also offers support for newer devices, such as foldable devices and windowing environments like Chrome OS.
The new Windows Manager APIs include the following.
- WindowLayoutInfo: Contains the display characteristics of a window, such as whether the window contains folds or hinges
- FoldingFeature: Enables you to monitor the folding state of a foldable device to determine its posture
- WindowMetrics: Provides metrics for the current window or the overall display of metrics
Jetpack WindowManager is not bundled with Android, allowing faster iteration of the API to quickly support the rapidly evolving device market and enabling application developers to adopt library updates without waiting for the latest Android release.
Now that the library is in beta, we encourage all developers to adopt Jetpack WindowManager, which has device-independent apis, testing apis, and comes with WindowMetrics so you can easily cope with window size changes. The gradual transition to beta means you can have confidence in the apis you’re using, allowing you to fully focus on building exciting experiences on these devices. Jetpack WindowManager supports functionality detection down to API 14.
The library
Jetpack WindowManager is a modern, Kotlin-led library that supports new device morphology factors and provides “appCompat-like” capabilities to build applications with responsive user interfaces.
Folding state
The most obvious feature this library provides is support for foldable devices. Applications can receive events when the device’s collapsed state changes, allowing the user interface to be updated to support new user interactions.
Google Duo on the Samsung Galaxy Z Fold2
Take a look at this Google Duo case study that shows how to add support for foldable devices.
There are two possible folded states: flat and semi-open. For FLAT, you can think of the surface as being completely FLAT and open, although in some cases it may be divided by hinges. For HALF_OPENED, the window has at least two logical areas. Below, we have pictures illustrating the possibilities for each state.
Folded state. Flat and semi-open
When the application is active, the application can receive information about the change in the collapse state by collecting events from the Kotlin stream. To start and stop event collection, we can use a lifecycle range, as explained in the repeatOnLifeCycle API Design story blog post and the code examples below.
lifecycleScope.launch(Dispatchers.Main) {
// The block passed to repeatOnLifecycle is executed when the lifecycle
// is at least STARTED and is cancelled when the lifecycle is STOPPED.
// It automatically restarts the block when the lifecycle is STARTED again.
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
// Safely collects from windowInfoRepository when the lifecycle is STARTED
// and stops collection when the lifecycle is STOPPED.
windowInfoRepository.windowLayoutInfo
.collect { newLayoutInfo ->
updateStateLog(newLayoutInfo)
updateCurrentState(newLayoutInfo)
}
}
}
Copy the code
The application can then use the information available in the WindowLayoutInfo object received to update the application’s layout as it becomes visible to the user.
The FoldingFeature includes information such as hinge orientation and whether folding features create two logical screen areas (isSeparating attributes). We can use these values to check whether the device is in tabletop mode (half open, hinge level).
The device is in TableTop mode
private fun isTableTopMode(foldFeature: FoldingFeature) =
foldFeature.isSeparating &&
foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL
Copy the code
Or in book mode (half open, hinge vertical).
The device is in book mode
private fun isBookMode(foldFeature: FoldingFeature) =
foldFeature.isSeparating &&
foldFeature.orientation == FoldingFeature.Orientation.VERTICAL
Copy the code
You can see an example of how to do this for a media player application in the article desktop Mode on a Foldable Device.
Note: It is important to collect these events on the main /UI thread to avoid synchronization issues between the UI and processing these events.
Support for responsive UI
Because screen sizes change so frequently in Android, it’s important to start designing fully adaptive and responsive UIs. Another feature included in the WindowManager library is the ability to retrieve current and maximum window measurement information. This is similar to the information provided by the framework WindowMetrics API included in API 30, but it is backward compatible with API 14.
Jetpack WindowManager provides two ways to retrieve WindowMetrics information, either as a stream of events or synchronously through the WindowMetricsCalculator class.
When writing code in a view where an asynchronous API might be too difficult to handle (such as onMeasure), use a WindowMetricsCalculator.
val windowMetrics =
WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(activity)
Copy the code
Another use case is in testing (see testing below).
For higher-level processing of the application UI, use WindowInfoRepository#currentWindowMetrics to get notifications of libraries when there is a window size change, independent of whether the change triggers a configuration change.
Here is an example of how to switch your layout based on the size of your available area.
// Create a new coroutine since repeatOnLifecycle is a suspend function lifecycleScope.launch(Dispatchers.Main) { // The block passed to repeatOnLifecycle is executed when the lifecycle // is at least STARTED and is cancelled when the lifecycle is STOPPED. // It automatically restarts the block when the lifecycle is STARTED again. lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { // Safely collect from currentWindowMetrics when the lifecycle is STARTED // and stops collection when the lifecycle is STOPPED windowInfoRepository.currentWindowMetrics .collect { windowMetrics -> val currentBounds = windowMetrics.bounds Log.i(TAG, "New bounds: {$currentBounds}") // We can update the layout if needed from here } } }Copy the code
The callback adapter to use the library in the Java programming language, or to use the callback interface, include the Androidx. window: window-Java dependency in your application. Provided WindowInfoRepositoryCallbackAdapter the artifacts, you can use it to register (a callback, and cancel the registration) to receive information and window measurement equipment update.
public class SplitLayoutActivity extends AppCompatActivity {
private WindowInfoRepositoryCallbackAdapter windowInfoRepository;
private ActivitySplitLayoutBinding binding;
private final LayoutStateChangeCallback layoutStateChangeCallback =
new LayoutStateChangeCallback();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivitySplitLayoutBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
windowInfoRepository =
new WindowInfoRepositoryCallbackAdapter(WindowInfoRepository.getOrCreate(this));
}
@Override
protected void onStart() {
super.onStart();
windowInfoRepository.addWindowLayoutInfoListener(Runnable::run, layoutStateChangeCallback);
}
@Override
protected void onStop() {
super.onStop();
windowInfoRepository.removeWindowLayoutInfoListener(layoutStateChangeCallback);
}
class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> {
@Override
publicvoid accept(WindowLayoutInfo windowLayoutInfo) { binding.splitLayout.updateWindowLayout(windowLayoutInfo); }}}Copy the code
test
We’ve heard from developers that stronger testing apis are critical to maintaining long-term support. Let’s talk about how to test foldable posture on a normal device.
So far, we’ve seen that the Jetpack WindowManager library notifies your application when the device posture changes, so you can modify the layout of your application.
The library in androidx. Window: window – provides WindowLayoutInfoPublisherRule in testing, it can make you with the support of testing FoldingFeature release WindowInfoLayout.
import androidx.window.testing.layout.FoldingFeature
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
Copy the code
We can use it to create a fake FoldingFeature to use in our tests.
val feature = FoldingFeature(
activity = activity,
center = center,
size = 0,
orientation = VERTICAL,
state = HALF_OPENED
)
val expected =
WindowLayoutInfo.Builder().setDisplayFeatures(listOf(feature)).build()
publisherRule.overrideWindowLayoutInfo(expected)
Copy the code
Then use WindowLayoutInfoPublisherRule to release it.
val publisherRule = WindowLayoutInfoPublisherRule()
publisherRule.overrideWindowLayoutInfo(expected)
Copy the code
The final step is to use the available Espresso matchers to check that the layout of the activity we are testing is as expected.
Here is an example of a test publishing FoldingFeature that has a vertical hinge of HALF_OPENED in the center of the screen.
@Test
fun testDeviceOpen_Vertical(a): Unit = testScope.runBlockingTest {
activityRule.scenario.onActivity { activity ->
val feature = FoldingFeature(
activity = activity,
orientation = VERTICAL,
state = HALF_OPENED
)
val expected =
WindowLayoutInfo.Builder().setDisplayFeatures(listOf(feature)).build()
val value = testScope.async {
activity.windowInfoRepository().windowLayoutInfo.first()
}
publisherRule.overrideWindowLayoutInfo(expected)
runBlockingTest {
Assert.assertEquals(
expected,
value.await()
)
}
}
// Checks that start_layout is on the left of end_layout with a vertical folding feature.
// This requires to run the test on a big enough screen to fit both views on screen
onView(withId(R.id.start_layout))
.check(isCompletelyLeftOf(withId(R.id.end_layout)))
}
Copy the code
Take a look at it in action. Code sample
A recent sample on GitHub shows how to use the Jetpack WindowManager library to retrieve display posture information, Collect information from the WindowLayoutInfo stream or through WindowInfoRepositoryCallbackAdapter register a callback.
The sample also includes tests that can be run on any device or emulator.
Use Windows Manager in your application
Foldable and dual-screen devices are no longer experimental or futuristic — large display areas and additional poses have proven user value, and there are now more devices available for your users to use. Foldable devices and dual-screen devices represent a natural evolution of smartphones. For Android developers, they offer access to a growing premium market, helped by a renewed focus by device makers.
We launched Jetpack WindowManager Alpha01 last year. Since then, the library has grown steadily, with some big improvements to the early feedback. The library has now embraced Android’s Kotlin first philosophy, moving from callback-driven models to Coroutines and flows. With Windows Manager now in beta, the API is stable and we strongly recommend adoption. And the update doesn’t stop there. We plan to add more functionality to the library and evolve it into an unbundled AppCompat for system UI, enabling developers to easily implement modern, responsive UI across all Android devices.
Keep the feedback flowing!
If you’d like to see more resources on optimizing for foldable devices and other large-screen devices, please visit this page.
www.deepl.com translation