Pietro F. Maggi (he/him)
Android Developer Relations Engineer
@pfmaggi
Building Android UIs
for any screen size
Slide 2
Slide 2 text
● Jetpack Compose 1.1 beta
● Window Size Classes new
● Embedded Activity new
● SlidingPaneLayout
○ Navigation beta
○ Preferences alpha
● 12L feature drop new
● Material Design
Adaptive Guidance new
● Reference Devices new
● Resizeable Emulator new
● Visual Linting new
Slide 3
Slide 3 text
bit.ly/ADS21-LS
Building for Large Screens
ADS21 Playlist
Slide 4
Slide 4 text
Design
Guidance
Views Jetpack
Compose
Testing
Slide 5
Slide 5 text
Large screens are growing in reach
100M
New Android tablets
92%
YoY ChromeOS growth
250M
Active large screen
Android devices today
12 month growth
1: IDC Quarterly Personal Computing Device Tracker, 2021Q2
1
Slide 6
Slide 6 text
Large screens are growing in reach
265%
Growth in Foldables
12 month growth
2: IDC Quarterly Mobile Phone Tracker, 2021Q2
2
Slide 7
Slide 7 text
Build your app for the
ecosystem,
increase your reach
Slide 8
Slide 8 text
No content
Slide 9
Slide 9 text
No content
Slide 10
Slide 10 text
bit.ly/ADS21-LS
Building for Large Screens
ADS21 Playlist
Slide 11
Slide 11 text
Jetpack
WindowManager
Android 12L
Slide 12
Slide 12 text
Jetpack WindowManager 1.0
Works with 12L and prior platform
versions to bring organic large-screen
support to your apps.
Slide 13
Slide 13 text
2020 | Confidential and Proprietary
WindowMetrics
maxWindowMetrics
currentWindowMetrics
● Building Android UIs for any screen size
Slide 14
Slide 14 text
class MyActivity : Activity() {
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
val windowMetrics = WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(this)
}
}
WindowMetrics
val testFeature = FoldingFeature(
activity: Activity,
center: Int = -1,
size: Int = 0,
state: State = HALF_OPENED,
orientation: Orientation = VERTICAL,
)
val expected = TestWindowLayoutInfo(listOf(feature))
Test instances
Slide 26
Slide 26 text
private val activityRule = ActivityScenarioRule(TestActivity::class.java)
private val publisherRule = WindowLayoutInfoPublisherRule()
@get:Rule
public val testRule: TestRule
init {
testRule = RuleChain.outerRule(publisherRule).around(activityRule)
}
Test rules
Slide 27
Slide 27 text
@Test
public fun testMyFeature() {
val feature = TestWindowLayoutInfo(emptyList())
publisherRule.overrideWindowLayoutInfo(feature)
activityRule.scenario.onActivity { activity ->
// App Specific Test
}
}
Test WindowLayoutInfo
Slide 28
Slide 28 text
@Test
fun testDeviceOpen_TableTop() {
activityRule.scenario.onActivity { activity ->
val feature =
FoldingFeature(activity = activity, state = HALF_OPENED, orientation = HORIZONTAL)
val expected = TestWindowLayoutInfo(listOf(feature))
publisherRule.overrideWindowLayoutInfo(expected)
}
onView(withSubstring("state = HALF_OPENED")).check(matches(isDisplayed()))
onView(withSubstring("are separated")).check(matches(isDisplayed()))
onView(withSubstring("Hinge is horizontal")).check(matches(isDisplayed()))
}
Test WindowLayoutInfo
Slide 29
Slide 29 text
@Test
fun testDeviceOpen_TableTop() {
activityRule.scenario.onActivity { activity ->
val feature =
FoldingFeature(activity = activity, state = HALF_OPENED, orientation = HORIZONTAL)
val expected = TestWindowLayoutInfo(listOf(feature))
publisherRule.overrideWindowLayoutInfo(expected)
}
onView(withSubstring("state = HALF_OPENED")).check(matches(isDisplayed()))
onView(withSubstring("are separated")).check(matches(isDisplayed()))
onView(withSubstring("Hinge is horizontal")).check(matches(isDisplayed()))
}
Test WindowLayoutInfo
Slide 30
Slide 30 text
@Test
fun testDeviceOpen_TableTop() {
activityRule.scenario.onActivity { activity ->
val feature =
FoldingFeature(activity = activity, state = HALF_OPENED, orientation = HORIZONTAL)
val expected = TestWindowLayoutInfo(listOf(feature))
publisherRule.overrideWindowLayoutInfo(expected)
}
onView(withSubstring("state = HALF_OPENED")).check(matches(isDisplayed()))
onView(withSubstring("are separated")).check(matches(isDisplayed()))
onView(withSubstring("Hinge is horizontal")).check(matches(isDisplayed()))
}
Test WindowLayoutInfo
Slide 31
Slide 31 text
Activity
Embedding
Slide 32
Slide 32 text
Side-by-side activities
Slide 33
Slide 33 text
main_split_config.xml
Slide 34
Slide 34 text
main_split_config.xml
Slide 35
Slide 35 text
main_split_config.xml
Slide 36
Slide 36 text
main_split_config.xml
Slide 37
Slide 37 text
More information
● Available with 12L Emulator
● Experimental in Jetpack WindowManager v1.0.0-beta03+
d.android.com/guide/topics/large-screens/activity-embedding
Slide 38
Slide 38 text
Additional resources
● goo.gle/foldables
● Best practices for video apps on
foldables and large screens
Resizable UI checklist
❏ Optimize your application’s layout for ‘Compact’ and ‘Expanded’ widths
❏ Test it across all reference devices, and pick the better layout for ‘Medium’
❏ Consider additional improvements, including custom layouts and input
support
Slide 50
Slide 50 text
goo.gle/window-size-classes
goo.gle/jetnews
Slide 51
Slide 51 text
Design
Adaptive UI
Slide 52
Slide 52 text
No content
Slide 53
Slide 53 text
Material
Design
guidance
material.io/design/layout
Slide 54
Slide 54 text
No content
Slide 55
Slide 55 text
bit.ly/ADS21-LS
ADS21 Playlist
Design beautiful apps on foldables
and large screens
Slide 56
Slide 56 text
Design
Guidance
Views Jetpack
Compose
Testing
Slide 57
Slide 57 text
Views
Adaptive UI
Slide 58
Slide 58 text
No content
Slide 59
Slide 59 text
https://github.com/android/trackr
Slide 60
Slide 60 text
No content
Slide 61
Slide 61 text
Trackr Navigation Graph
Tasks Details Edit/ New Se ings Archive
Bo om App Bar
New Task
Slide 62
Slide 62 text
bit.ly/ADS21-LS
Building for Large Screens
ADS21 Playlist
Slide 63
Slide 63 text
Two Pane
SlidingPaneLayout
Slide 64
Slide 64 text
Trackr Navigation Graph
Tasks Details Edit/ New Se ings Archive
NavigationRailView
New Task
Slide 65
Slide 65 text
Trackr Navigation Graph
TwoPaneTasks
Details
Edit/ New Se ings Archive
New Task
Edit Task
NavigationRailView
Slide 66
Slide 66 text
Trackr Navigation Graph
TwoPaneTasks
Details
Edit/ New Se ings Archive
New Task
Edit Task
NavigationRailView
Slide 67
Slide 67 text
bit.ly/ADS21-LS
Building for Large Screens
ADS21 Playlist
@Composable
fun Activity.rememberWindowSizeClass(): WindowSize {
val configuration = LocalConfiguration.current
val windowMetrics = remember(configuration) {
WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(this)
}
val windowDpSize = with(LocalDensity.current) {
windowMetrics.bounds.toComposeRect().size.toDpSize()
}
when {
windowDpSize.width < 600.dp -> WindowSize.Compact
windowDpSize.width < 840.dp -> WindowSize.Medium
else -> WindowSize.Expanded
}
}
Window Size Classes
Slide 77
Slide 77 text
@Composable
fun Activity.rememberWindowSizeClass(): WindowSize {
val configuration = LocalConfiguration.current
val windowMetrics = remember(configuration) {
WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(this)
}
val windowDpSize = with(LocalDensity.current) {
windowMetrics.bounds.toComposeRect().size.toDpSize()
}
when {
windowDpSize.width < 600.dp -> WindowSize.Compact
windowDpSize.width < 840.dp -> WindowSize.Medium
else -> WindowSize.Expanded
}
}
Window Size Classes
Slide 78
Slide 78 text
@Composable
fun Activity.rememberWindowSizeClass(): WindowSize {
val configuration = LocalConfiguration.current
val windowMetrics = remember(configuration) {
WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(this)
}
val windowDpSize = with(LocalDensity.current) {
windowMetrics.bounds.toComposeRect().size.toDpSize()
}
when {
windowDpSize.width < 600.dp -> WindowSize.Compact
windowDpSize.width < 840.dp -> WindowSize.Medium
else -> WindowSize.Expanded
}
}
Window Size Classes
Slide 79
Slide 79 text
@Composable
fun Activity.rememberWindowSizeClass(): WindowSize {
val configuration = LocalConfiguration.current
val windowMetrics = remember(configuration) {
WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(this)
}
val windowDpSize = with(LocalDensity.current) {
windowMetrics.bounds.toComposeRect().size.toDpSize()
}
when {
windowDpSize.width < 600.dp -> WindowSize.Compact
windowDpSize.width < 840.dp -> WindowSize.Medium
else -> WindowSize.Expanded
}
}
Window Size Classes
Slide 80
Slide 80 text
@Composable
fun Activity.rememberWindowSizeClass(): WindowSize {
val configuration = LocalConfiguration.current
val windowMetrics = remember(configuration) {
WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(this)
}
val windowDpSize = with(LocalDensity.current) {
windowMetrics.bounds.toComposeRect().size.toDpSize()
}
when {
windowDpSize.width < 600.dp -> WindowSize.Compact
windowDpSize.width < 840.dp -> WindowSize.Medium
else -> WindowSize.Expanded
}
}
Window Size Classes
Slide 81
Slide 81 text
bit.ly/ADS21-LS
Building for Large Screens
ADS21 Playlist
Slide 82
Slide 82 text
Design
Guidance
Views Jetpack
Compose
Testing
Slide 83
Slide 83 text
Testing
Adaptive UI
Slide 84
Slide 84 text
Gradle Managed Devices
Define virtual devices and device
groups using Gradle DSL for easy
integration into CI.
Device lifecycle fully managed by
Android Gradle plugin (AGP)
AGP downloads any required
images and SDK components
Caches test results and utilizes
emulator snapshots for faster
continuous testing
Gradle
Managed
Devices
Unified Test
Platform
Automated Test Devices (ATD)
Headless and weightless Optimized testing
Reduces CPU and Memory
usage and disables hardware
rendering. You can still
perform normal screenshot
testing using AndroidX.Test.
Optimizes apps and
services to only those
typically required for
instrumented testing.
From versatile device
profiles to Snapshots,
Gradle Managed Devices,
and Test Sharding, many of
the great benefits of
Emulators are still
available.
Familiar functionality
Slide 90
Slide 90 text
ATD
AVD today
+ +
=
Automated Test Device (ATD)
AVD today
S + S + S
=
// Tests synchronous screen rotation on a device.
@Test
fun testItOnDevice() {
onDevice().setScreenOrientation(ScreenOrientation.PORTRAIT))
}
// Tests on a device that supports the tabletop mode.
@Test
fun testItOnFoldInFoldable() {
onDevice().setTableTopMode()
onDevice().setFlatMode()
}
Device Controller APIs (Preview soon)
● Jetpack Compose 1.1 beta
● Window Size Classes new
● Embedded Activity new
● SlidingPaneLayout
○ Navigation beta
○ Preferences alpha
● 12L feature drop new
● Material Design
Adaptive Guidance new
● Reference Devices new
● Resizeable Emulator new
● Visual Linting new
Slide 96
Slide 96 text
d.android.com/large-screens
Slide 97
Slide 97 text
Thank you!
Pietro F. Maggi (he/him)
Android Developer Relations Engineer
@pfmaggi
Slide 98
Slide 98 text
End of deck
Pietro F. Maggi (he/him)
Android Developer Relations Engineer
@pfmaggi
Slide 99
Slide 99 text
End of deck - for real
Pietro F. Maggi (he/him)
Android Developer Relations Engineer
@pfmaggi