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

Why is adaptive layout a nightmare?

Gerard
February 23, 2024

Why is adaptive layout a nightmare?

Dive into the captivating realm of Android UI development in this session where we will share in-depth expertise on designing versatile user interfaces. Together, we will explore the complex challenges of organizing and building UI components, providing unique insights into crafting seamless user experiences across a variety of devices such as phones, foldables, and tablets.

While Google advocates for the use of adaptive layout, we will unveil the often underestimated nuances, shed practical light on overcoming obstacles you’ll meet down the road and help you create truly adaptive Android applications.

Gerard

February 23, 2024
Tweet

More Decks by Gerard

Other Decks in Programming

Transcript

  1. app main navigation Feature1ScreenVM Feature2ScreenVM organism comp. organism comp. organism

    comp. organism comp. organism comp. organism comp. Feature1Screen Feature2Screen molecule comp. molecule comp. molecule comp. molecule comp. molecule comp. molecule comp.
  2. app main navigation Feature1ScreenVM Feature2ScreenVM organism organism organism organism organism

    organism Feature1Screen Feature2Screen molecule molecule molecule molecule molecule molecule FeatureTabletScreen
  3. app main navigation Feature1ScreenVM Feature2ScreenVM organism organism organism organism organism

    organism Feature1Screen Feature2Screen molecule molecule molecule molecule molecule molecule NavigationLayout
  4. app shared shared-di models style schedules speakers partners networking infos

    event-list Android App Feature module Core modules feature ui di main Main navigation di
  5. app shared shared-di models style schedules speakers partners networking infos

    event-list Android App Feature module Core modules feature screens ui main di schedules speakers partners … UI components Main navigation di
  6. @Composable fun SmallScheduleCard( title: String, speakersUrls: ImmutableList<String>, speakersLabel: String, contentDescription:

    String?, onClick: () !" Unit, modifier: Modifier = Modifier, isFavorite: Boolean = false, colors: ScheduleCardColors = ScheduleCardDefaults.cardColors(), style: TextStyle = ScheduleCardDefaults.smallTextStyle, shape: Shape = ScheduleCardDefaults.smallShape, onFavoriteClick: (() !" Unit)? = null, bottomBar: (@Composable FlowRowScope.() !" Unit)? = null ) :styles:schedules
  7. @Composable fun MediumScheduleCard( title: String, speakersUrls: ImmutableList<String>, speakersLabel: String, contentDescription:

    String?, onClick: () !" Unit, modifier: Modifier = Modifier, isFavorite: Boolean = false, colors: ScheduleCardColors = ScheduleCardDefaults.cardColors(), style: TextStyle = ScheduleCardDefaults.mediumTextStyle, shape: Shape = ScheduleCardDefaults.mediumShape, onFavoriteClick: (() !" Unit)? = null, topBar: (@Composable RowScope.() !" Unit)? = null, bottomBar: (@Composable RowScope.() !" Unit)? = null ) :styles:schedules
  8. START WORKING ON THE LANDSCAPE MODE ▸ Size variants ▸

    Adapts UI based on the window configuration
  9. :schedules:screens @Composable fun AgendaFiltersCompactVM( filtersUi: FiltersUi, onFavoriteClick: (selected: Boolean) !"

    Unit, onCategoryClick: (categoryUi: CategoryUi, selected: Boolean) !" Unit, onFormatClick: (formatUi: FormatUi, selected: Boolean) !" Unit, modifier: Modifier = Modifier, containerColor: Color = MaterialTheme.colorScheme.background, navigationIcon: @Composable (AppBarIcons.() !" Unit)? = null )
  10. :schedules:feature @Composable fun ScheduleGridVM( onFilterClicked: () !" Unit, onTalkClicked: (id:

    String) !" Unit, showFilterIcon: Boolean, modifier: Modifier = Modifier, isSmallSize: Boolean = false, viewModel: ScheduleGridViewModel = koinViewModel() )
  11. if (configuration.orientation !# Configuration.ORIENTATION_LANDSCAPE) { Row { ScheduleGridVM( !$ parameters

    !% modifier = Modifier.weight(1f), showFilterIcon = false, onFilterClicked = { !$ never triggered !% } ) AgendaFiltersCompactVM( !$ parameters !% modifier = Modifier.weight(1f), ) } } else { ScheduleGridVM( !$ parameters !% showFilterIcon = true, onFilterClicked = onFilterClicked !& will open a new screen ) }
  12. @Composable fun NavigationSuiteScaffold( navigationSuiteItems: NavigationSuiteScope.() !" Unit, modifier: Modifier =

    Modifier, layoutType: NavigationSuiteType = NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(WindowAdaptiveInfoDefault), navigationSuiteColors: NavigationSuiteColors = NavigationSuiteDefaults.colors(), containerColor: Color = MaterialTheme.colorScheme.background, contentColor: Color = contentColorFor(containerColor), content: @Composable () !" Unit = {}, )
  13. @Composable fun AppNavigation( routeSelected: String, modifier: Modifier = Modifier, navController:

    NavHostController = rememberNavController(), ) { NavigationSuiteScaffold( modifier = modifier, navigationSuiteItems = { navActions.forEach { action !" val selected = action.route !# routeSelected item( selected = selected, icon = { Icon( imageVector = if (selected) action.iconSelected else action.icon, contentDescription = action.contentDescription ) }, onClick = { !& handle navigation } ) } } ) { NavHost( navController = navController, startDestination = "agenda", builder = { !& composables } ) } } :main:navigation
  14. @Composable fun SupportingPaneScaffold( supportingPane: @Composable ThreePaneScaffoldScope.() !" Unit, modifier: Modifier

    = Modifier, scaffoldState: ThreePaneScaffoldState = calculateSupportingPaneScaffoldState(), windowInsets: WindowInsets = SupportingPaneScaffoldDefaults.windowInsets, extraPane: (@Composable ThreePaneScaffoldScope.() !" Unit)? = null, mainPane: @Composable ThreePaneScaffoldScope.() !" Unit )
  15. @Composable fun ScheduleGridAdaptive( onFilterClicked: () !" Unit, onTalkClicked: (id: String)

    !" Unit, showFilterIcon: Boolean, modifier: Modifier = Modifier, isSmallSize: Boolean = false, ) { SupportingPaneScaffold( modifier = modifier, mainPane = { ScheduleGridVM( onFilterClicked = onFilterClicked, onTalkClicked = onTalkClicked, showFilterIcon = showFilterIcon, isSmallSize = isSmallSize ) }, supportingPane = { AgendaFiltersCompactVM( containerColor = if (showFilterIcon) MaterialTheme.colorScheme.background else MaterialTheme.colorScheme.surfaceContainerHigh ) } ) } :schedules:feature
  16. @Composable fun ListDetailPaneScaffold( listPane: @Composable ThreePaneScaffoldScope.() !" Unit, modifier: Modifier

    = Modifier, scaffoldState: ThreePaneScaffoldState = calculateListDetailPaneScaffoldState(), windowInsets: WindowInsets = ListDetailPaneScaffoldDefaults.windowInsets, extraPane: (@Composable ThreePaneScaffoldScope.() !" Unit)? = null, detailPane: @Composable ThreePaneScaffoldScope.() !" Unit )
  17. @Composable fun SpeakerAdaptive( showBackInDetail: Boolean, onTalkClicked: (id: String) !" Unit,

    onLinkClicked: (url: String) !" Unit, modifier: Modifier = Modifier ) { val navigator = rememberListDetailPaneScaffoldNavigator() var selectedItem: String? by rememberSaveable { mutableStateOf(null) } ListDetailPaneScaffold( scaffoldState = navigator.scaffoldState, modifier = modifier, listPane = { !& list declaration }, detailPane = { !& detail declaration } ) } :speakers:feature
  18. @Composable fun SpeakerAdaptive( showBackInDetail: Boolean, onTalkClicked: (id: String) !" Unit,

    onLinkClicked: (url: String) !" Unit, modifier: Modifier = Modifier ) { val navigator = rememberListDetailPaneScaffoldNavigator() var selectedItem: String? by rememberSaveable { mutableStateOf(null) } ListDetailPaneScaffold( scaffoldState = navigator.scaffoldState, modifier = modifier, listPane = { SpeakersListCompactVM( onSpeakerClicked = { selectedItem = it navigator.navigateTo(ListDetailPaneScaffoldRole.Detail) } ) }, detailPane = { !& detail implementation } ) } :speakers:feature
  19. @Composable fun SpeakerAdaptive( showBackInDetail: Boolean, onTalkClicked: (id: String) !" Unit,

    onLinkClicked: (url: String) !" Unit, modifier: Modifier = Modifier ) { val navigator = rememberListDetailPaneScaffoldNavigator() var selectedItem: String? by rememberSaveable { mutableStateOf(null) } ListDetailPaneScaffold( scaffoldState = navigator.scaffoldState, modifier = modifier, listPane = { !& list implementation }, detailPane = { AnimatedPane(modifier = Modifier) { selectedItem!'let { item !" SpeakerDetailOrientableVM( speakerId = item, onTalkClicked = onTalkClicked, onLinkClicked = onLinkClicked, navigationIcon = if (showBackInDetail) { @Composable { Back { if (navigator.canNavigateBack()) { navigator.navigateBack() } } } } else null ) } } } ) } :speakers:feature
  20. app shared shared-di models style schedules speakers partners networking infos

    event-list main schedules speakers partners … FloatingActionButton declaration Screen Level
  21. app shared shared-di models style schedules speakers partners networking infos

    event-list main schedules speakers partners … FloatingActionButton declaration App Level
  22. FRICTION POINTS ▸ Conflict in Material Design specifications ▸ Floating

    Action Button ▸ WindowSizeClass not available for sub-components
  23. REFERENCES ▸ Material 3 - Understanding layout https://m3.material.io/foundations/layout/ understanding-layout/overview ▸

    Material 3 - Applying layout https://m3.material.io/foundations/layout/ applying-layout/window-size-classes ▸ Material 3 - Canonical layouts https://m3.material.io/foundations/layout/ canonical-layouts/overview ▸ Large screens gallery https://developer.android.com/large-screens/gallery/ ▸ Now in Android https://github.com/android/nowinandroid ▸ Conferences4Hall https://github.com/GerardPaligot/conferences4hall