Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building Android UIs for any screen size

Building Android UIs for any screen size

The rising popularity of tablets and foldable devices unlocks new opportunities to address a new range of users in new ways. A responsive UI allows to target these users in a new and engaging way.
In this talk you’ll get an understanding of what’s available for developers to support large screens. We’ll cover new APIs like Compose for large screens and Jetpack WindowManager, Android Studio templates and more. By the end of this talk, you’ll have all the skills you need to create and test responsive UIs on Android so that users love your app no matter what device they’re using it on.

Pietro F. Maggi

November 11, 2021
Tweet

More Decks by Pietro F. Maggi

Other Decks in Programming

Transcript

  1. • 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
  2. 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
  3. Large screens are growing in reach 265% Growth in Foldables

    12 month growth 2: IDC Quarterly Mobile Phone Tracker, 2021Q2 2
  4. Jetpack WindowManager 1.0 Works with 12L and prior platform versions

    to bring organic large-screen support to your apps.
  5. class MyActivity : Activity() { override fun onConfigurationChanged(newConfig: Configuration) {

    super.onConfigurationChanged(newConfig) val windowMetrics = WindowMetricsCalculator.getOrCreate() .computeCurrentWindowMetrics(this) } } WindowMetrics
  6. FoldingFeature Orientation: Vertical State: Flat Separating: true Occlusion: Full Orientation:

    Horizontal State: Half-folded Separating: true Occlusion: None
  7. 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
  8. 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
  9. @Test public fun testMyFeature() { val feature = TestWindowLayoutInfo(emptyList()) publisherRule.overrideWindowLayoutInfo(feature)

    activityRule.scenario.onActivity { activity -> // App Specific Test } } Test WindowLayoutInfo
  10. @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
  11. @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
  12. @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
  13. More information • Available with 12L Emulator • Experimental in

    Jetpack WindowManager v1.0.0-beta03+ d.android.com/guide/topics/large-screens/activity-embedding
  14. class WindowMetrics { class WindowSizeClass(val name: String) { companion object

    { val COMPACT = WindowSizeClass("COMPACT") val MEDIUM = WindowSizeClass("MEDIUM") val EXPANDED = WindowSizeClass("EXPANDED") } } val widthClass: WindowSizeClass get() { ... } val heightClass: WindowSizeClass get() { ... } } Window Size Classes (subject to change) Window Manager
  15. 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
  16. @Composable fun Card() { if (small) { Column { Title(...)

    Subtitle(...) } } else { Column { Image(...) Title(...) Subtitle(...) } } } Jetpack Compose
  17. @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
  18. @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
  19. @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
  20. @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
  21. @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
  22. 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
  23. 85 testOptions { devices { pixel2api29 (com.android.build.api.dsl.ManagedVirtualDevice) { ... }

    nexus9api30 (com.android.build.api.dsl.ManagedVirtualDevice) { device = "Nexus 9" apiLevel = 30 systemImageSource = "google" abi = "x86" } } }
  24. 86 testOptions { devices { pixel2api29 (com.android.build.api.dsl.ManagedVirtualDevice) { ... }

    nexus9api30 (com.android.build.api.dsl.ManagedVirtualDevice) { ... } deviceGroups { mediumAndExpandedWidth{ targetDevices.addAll(devices.pixel2api29) targetDevices.addAll(devices.nexus9api30) } } } }
  25. 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
  26. 91 testOptions { devices { pixel2api29 (com.android.build.api.dsl.ManagedVirtualDevice) { ... }

    nexus9api30 (com.android.build.api.dsl.ManagedVirtualDevice) { device = "Nexus 9" apiLevel = 30 systemImageSource = "aosp-atd" // or "google-atd" abi = "x86" } } }
  27. // 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)
  28. • 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
  29. End of deck - for real Pietro F. Maggi (he/him)

    Android Developer Relations Engineer @pfmaggi