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

Adaptive Layout for Any Screen with Jetpack Compose [Dicoding Developer Conference]

Adaptive Layout for Any Screen with Jetpack Compose [Dicoding Developer Conference]

Outline:
Material Design Guidance
Adaptive Navigation in Compose
Canonical Layout in Compose
Foldable Phone
App Quality

Ahmad Arif Faizin

February 11, 2023
Tweet

More Decks by Ahmad Arif Faizin

Other Decks in Technology

Transcript

  1. Adaptive Layout
    for Any Screen
    with Jetpack
    Compose
    Ahmad Arif
    Faizin
    Curriculum Developer
    at Dicoding

    View Slide

  2. Ahmad Arif Faizin
    Curriculum Developer at Dicoding
    Adaptive Layout for
    Any Screen with
    Jetpack Compose

    View Slide

  3. ● Material Design
    Guidance
    ● Adaptive Navigation in
    Compose
    ● List-Detail in Compose
    ● Foldable Device
    ● Large Screen App Quality
    Outline

    View Slide

  4. Source: Blog - What's new in foldables, tablets, and large screens

    View Slide

  5. Mobile App
    Developers
    Be Like…

    View Slide

  6. Source: Blog - What's new in foldables, tablets, and large screens

    View Slide

  7. 270M
    Active large screen
    Android devices

    View Slide

  8. Apps are Optimizing for Large Screen
    goo.gle/large-screens-case-studies

    View Slide

  9. View Slide

  10. Build your app for the
    ecosystem increase
    your reach

    View Slide

  11. Material Design
    Guidance
    01

    View Slide

  12. Understanding Anatomy
    1. Margin 2. Column 3. Gutter

    View Slide

  13. Breakpoints
    Breakpoint
    range (dp)
    Portrait Landscape
    Window size
    class
    Columns
    Minimum
    margins*
    0-599 Handset
    Phone in
    portrait
    Compact 4 8
    600-839
    Foldable
    Small tablet
    Foldable
    Small tablet
    Medium 12 12
    840+ Large tablet
    Large tablet
    Desktop
    Expanded 12 32

    View Slide

  14. Avoid using
    physical/hardware values
    for making layout
    decisions!

    View Slide

  15. Use
    Window size
    for layout
    decision!

    View Slide

  16. Material Measurement

    View Slide

  17. View Slide

  18. Proven Design Pattern: Canonical Layout

    View Slide

  19. List-Detail
    ● Best for browsing content and
    quickly seeing details.
    ● Recommended for 840+dp
    ● Key use case
    ○ Text message + conversation
    ○ File browser + open folder
    ○ Musical artist + album detail

    View Slide

  20. Feed
    ● Equivalent content elements in
    a configurable grid for quick,
    convenient viewing of a large
    amount of content.
    ● Recommended for 600+dp
    ● Key use case
    ○ News
    ○ Social media

    View Slide

  21. Supporting Panel
    ● The screen is divided between a
    focus panel (left) and a supporting
    panel (right).
    ● Recommended for 600+dp
    ● Key use case
    ○ Productivity
    ○ Document editing and
    commenting
    ○ Content and media browsing

    View Slide

  22. Component Swapping

    View Slide

  23. “Reach” Your Users on Large Screens

    View Slide

  24. Window size class Few items Many items
    compact width bottom navigation bar navigation drawer
    (leading edge or bottom)
    medium width navigation rail navigation drawer
    (leading edge)
    expanded width persistent navigation
    drawer (leading edge)
    persistent navigation
    drawer (leading edge)
    Adaptive Navigation

    View Slide

  25. Navigation Rail
    ● Side navigation
    component that
    displays three to
    seven app
    destinations
    ● Better ergonomics
    Easier reach on
    large screen
    ● Available in
    Material3

    View Slide

  26. Adaptive Navigation
    in Compose
    02

    View Slide

  27. Resizable
    Emulator

    View Slide

  28. View Slide

  29. Window Size
    Classes API

    View Slide

  30. WindowSizeClass Initialization
    implementation "androidx.compose.material3:material3-window-size-class:1.0.1"
    @OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
    ReplyTheme {
    val widthSizeClass = calculateWindowSizeClass(this).widthSizeClass
    ...
    }
    }
    }

    View Slide

  31. WindowSizeClass Implementation
    val navigationType: ReplyNavigationType //enum class
    when (widthSizeClass) {
    WindowWidthSizeClass.Compact -> {
    navigationType = ReplyNavigationType.BOTTOM_NAVIGATION
    }
    WindowWidthSizeClass.Medium -> {
    navigationType = ReplyNavigationType.NAVIGATION_RAIL
    }
    WindowWidthSizeClass.Expanded -> {
    navigationType = ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER
    }
    else -> {
    navigationType = ReplyNavigationType.BOTTOM_NAVIGATION
    }
    }

    View Slide

  32. Navigation Visibility Logic
    if (navigationType == PERMANENT_NAVIGATION_DRAWER) {
    PermanentNavigationDrawer(...) { ReplyAppContent(navigationType) }
    } else {
    ModalNavigationDrawer(...) { ReplyAppContent(navigationType)}
    }
    ...
    @Composable
    fun ReplyAppContent(navigationType: ReplyNavigationType) {
    Row(...) {
    AnimatedVisibility(visible = navigationType == NAVIGATION_RAIL) {
    NavigationRail() {...}
    }
    Column(...) {
    ListOnlyContent(...)
    AnimatedVisibility(visible = navigationType == BOTTOM_NAVIGATION) {
    NavigationBar() {...}
    }
    }

    View Slide

  33. Composable Preview

    View Slide

  34. View Slide

  35. List-Detail in
    Compose
    03

    View Slide

  36. ContentType Classification
    val contentType: ReplyContentType //enum class
    contentType = if (widthSizeClass == WindowWidthSizeClass.Expanded) {
    ReplyContentType.LIST_AND_DETAIL
    } else {
    ReplyContentType.LIST_ONLY
    }

    View Slide

  37. Previous Logic
    @Composable
    fun ReplyAppContent(navigationType: ReplyNavigationType) {
    Row(...) {
    AnimatedVisibility(visible = navigationType == NAVIGATION_RAIL) {
    NavigationRail() {...}
    }
    Column(...) {
    ListOnlyContent(...)
    AnimatedVisibility(visible = navigationType == BOTTOM_NAVIGATION) {
    NavigationBar() {...}
    }
    }
    }
    }

    View Slide

  38. Additional Content Type Logic
    if (contentType == ReplyContentType.LIST_AND_DETAIL) {
    Row {
    ListOnlyContent()
    DetailContent(selectedItemId)
    }
    } else {
    if (replyHomeUIState.isShowingHomepage) {
    ListOnlyContent()
    } else {
    DetailContent(selectedItemId)
    }
    }

    View Slide

  39. Screen State Logic in ViewModel
    //ketika item pada list dipilih
    fun updateDetailsScreenStates(email: Email) {
    _uiState.update {
    it.copy(
    currentSelectedEmail = email,
    isShowingHomepage = false
    )
    }
    }

    View Slide

  40. View Slide

  41. Wait a
    moment!

    View Slide

  42. View Slide

  43. Navigation Component vs Manual Navigation

    View Slide

  44. BackHandler
    if (contentType == ReplyContentType.LIST_AND_DETAIL) {
    ...
    } else {
    if (selectedItemId != null) {
    DetailContent(selectedItemId)
    BackHandler { /* handle returning to ListOnlyContent() */ }
    } else {
    ListOnlyContent()
    }
    }

    View Slide

  45. Additional Screen State Logic in ViewModel
    fun updateDetailsScreenStates(email: Email) {
    _uiState.update {
    it.copy(
    currentSelectedEmail = email,
    isShowingHomepage = false
    )
    }
    }
    fun resetHomeScreenStates() {
    _uiState.update {
    it.copy(
    currentSelectedEmail = it.currentSelectedEmail,
    isShowingHomepage = true
    )
    }
    }

    View Slide

  46. View Slide

  47. Is everything
    okay now?

    View Slide

  48. Foldable
    Device
    04

    View Slide

  49. Foldable
    Emulator
    *Use Microsoft Surface
    Duo emulator for dual
    screen emulator

    View Slide

  50. Device Posture
    windowLayoutInfo
    ● state()
    HALF_OPENED/FLAT
    ● orientation()
    VERTICAL /
    HORIZONTAL
    ● occlusionType()
    FULL/NONE
    ● isSeparating()
    ● bounds.rect()
    Book
    TableTop
    Tent
    Normal

    View Slide

  51. Sample Case

    View Slide

  52. Device Posture Classification
    val devicePostureFlow = WindowInfoTracker.getOrCreate(this).windowLayoutInfo(this)
    .flowWithLifecycle(this.lifecycle)
    .map { layoutInfo ->
    val foldingFeature = layoutInfo.displayFeatures
    .filterIsInstance().firstOrNull()
    when {
    isTableTopPosture(foldingFeature) -> DevicePosture.TableTopPosture
    else -> DevicePosture.NormalPosture
    }
    }
    .stateIn(
    scope = lifecycleScope,
    started = SharingStarted.Eagerly,
    initialValue = DevicePosture.NormalPosture //enum class
    )

    View Slide

  53. Device Posture Decision
    fun isTableTopPosture(foldFeature: FoldingFeature?): Boolean {
    return foldFeature?.state == FoldingFeature.State.HALF_OPENED &&
    foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL
    }
    fun isBookPosture(foldFeature: FoldingFeature?): Boolean {
    return foldFeature?.state == FoldingFeature.State.HALF_OPENED &&
    foldFeature.orientation == FoldingFeature.Orientation.VERTICAL
    }
    fun isSeparating(foldFeature: FoldingFeature?): Boolean {
    return foldFeature?.state == FoldingFeature.State.FLAT &&
    foldFeature.isSeparating
    }

    View Slide

  54. if (devicePosture == DevicePosture.TableTopPosture) {
    PlayerContentTableTop()
    } else {
    PlayerContentRegular()
    }

    View Slide

  55. View Slide

  56. Large Screen
    App Quality
    05

    View Slide

  57. Quality Tiers
    ● Tier 3 (Basic) 😐
    — Large screen ready
    ● Tier 2 (Better) 🙂
    — Large screen optimized
    ● Tier 1 (Best) 😃
    — Large screen differentiated

    View Slide

  58. Tier 3 (Basic) — Large screen ready
    ● Full screen
    ○ But app layout not ideal
    ● Basic support for external input
    ○ Keyboard
    ○ Mouse
    ○ Trackpad
    ● Handles configuration changes
    and retains or restores its state

    View Slide

  59. Tier 2 (Better) — Large screen optimized
    ● Layout Optimization
    ○ Leading edge navigation
    ○ Canonical Layout
    ○ Scaled Grid layouts
    ● Enhanced support for external
    input
    ○ Shortcut
    ○ Action by keyboard
    ○ Zooming using mouse
    ○ Hover

    View Slide

  60. Responsive UI elements

    View Slide

  61. Tier 3 (Best) — Large screen differentiated
    ● Multitasking
    ● Foldable posture
    ● Drag & Drop
    ● Stylus input
    ● Picture-in-picture mode

    View Slide

  62. Dialog Box Placement

    View Slide

  63. Test Your App
    // Checks start_layout is on the left of end_layout with a vertical folding
    feature.
    @Test
    fun testDeviceOpen_Vertical() {
    activityRule.scenario.onActivity { activity ->
    val feature = FoldingFeature(
    activity = activity,
    state = HALF_OPENED,
    orientation = VERTICAL)
    val expected = TestWindowLayoutInfo(listOf(feature))
    publisherRule.overrideWindowLayoutInfo(expected)
    }
    onView(withId(R.id.start_layout))
    .check(isCompletelyLeftOf(withId(R.id.end_layout)))
    }

    View Slide

  64. Further
    Learning

    View Slide

  65. https://m3.material.io/foundations/adaptive-design/canonical-layouts
    https://developer.android.com/large-screens/gallery
    https://www.youtube.com/watch?v=Z4h9cvlE66E
    https://github.com/android/user-interface-samples/tree/main/CanonicalLayouts
    https://codelabs.developers.google.com/jetpack-compose-adaptability
    https://www.youtube.com/watch?v=LTLQhC6VadI
    https://android-developers.googleblog.com/2021/05/whats-new-in-foldables-tablets-and.html
    https://android-developers.googleblog.com/2022/11/reach-your-users-on-large-screens.html
    https://developer.android.com/guide/topics/large-screens/large-screen-cookbook
    Reference

    View Slide

  66. Bukan hanya otak dan otot aja
    yang perlu adaptive,
    tapi juga UI~
    @arif_faizin, 2023

    View Slide

  67. Thank you
    Deck will be available at
    https://speakerdeck.com/arifaizin

    View Slide