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
  2. • Material Design Guidance • Adaptive Navigation in Compose •

    List-Detail in Compose • Foldable Device • Large Screen App Quality Outline
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. Navigation Rail • Side navigation component that displays three to

    seven app destinations • Better ergonomics Easier reach on large screen • Available in Material3
  9. 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 } }
  10. 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() {...} } }
  11. ContentType Classification val contentType: ReplyContentType //enum class contentType = if

    (widthSizeClass == WindowWidthSizeClass.Expanded) { ReplyContentType.LIST_AND_DETAIL } else { ReplyContentType.LIST_ONLY }
  12. Previous Logic @Composable fun ReplyAppContent(navigationType: ReplyNavigationType) { Row(...) { AnimatedVisibility(visible

    = navigationType == NAVIGATION_RAIL) { NavigationRail() {...} } Column(...) { ListOnlyContent(...) AnimatedVisibility(visible = navigationType == BOTTOM_NAVIGATION) { NavigationBar() {...} } } } }
  13. Additional Content Type Logic if (contentType == ReplyContentType.LIST_AND_DETAIL) { Row

    { ListOnlyContent() DetailContent(selectedItemId) } } else { if (replyHomeUIState.isShowingHomepage) { ListOnlyContent() } else { DetailContent(selectedItemId) } }
  14. Screen State Logic in ViewModel //ketika item pada list dipilih

    fun updateDetailsScreenStates(email: Email) { _uiState.update { it.copy( currentSelectedEmail = email, isShowingHomepage = false ) } }
  15. BackHandler if (contentType == ReplyContentType.LIST_AND_DETAIL) { ... } else {

    if (selectedItemId != null) { DetailContent(selectedItemId) BackHandler { /* handle returning to ListOnlyContent() */ } } else { ListOnlyContent() } }
  16. 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 ) } }
  17. Device Posture windowLayoutInfo • state() HALF_OPENED/FLAT • orientation() VERTICAL /

    HORIZONTAL • occlusionType() FULL/NONE • isSeparating() • bounds.rect() Book TableTop Tent Normal
  18. Device Posture Classification val devicePostureFlow = WindowInfoTracker.getOrCreate(this).windowLayoutInfo(this) .flowWithLifecycle(this.lifecycle) .map {

    layoutInfo -> val foldingFeature = layoutInfo.displayFeatures .filterIsInstance<FoldingFeature>().firstOrNull() when { isTableTopPosture(foldingFeature) -> DevicePosture.TableTopPosture else -> DevicePosture.NormalPosture } } .stateIn( scope = lifecycleScope, started = SharingStarted.Eagerly, initialValue = DevicePosture.NormalPosture //enum class )
  19. 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 }
  20. Quality Tiers • Tier 3 (Basic) 😐 — Large screen

    ready • Tier 2 (Better) 🙂 — Large screen optimized • Tier 1 (Best) 😃 — Large screen differentiated
  21. 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
  22. 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
  23. Tier 3 (Best) — Large screen differentiated • Multitasking •

    Foldable posture • Drag & Drop • Stylus input • Picture-in-picture mode
  24. 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))) }