Slide 1

Slide 1 text

@GerardPaligot @Smoochibi WHY IS ADAPTIVE LAYOUT A NIGHTMARE?

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

WHAT’S THE ISSUE?

Slide 5

Slide 5 text

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.

Slide 6

Slide 6 text

organism organism organism organism organism organism molecule molecule molecule molecule molecule molecule FeatureTabletScreen app main navigation Feature1ScreenVM Feature2ScreenVM Feature1Screen Feature2Screen 9:30

Slide 7

Slide 7 text

9:30 app main navigation Feature1ScreenVM Feature2ScreenVM organism organism organism organism organism organism Feature1Screen Feature2Screen molecule molecule molecule molecule molecule molecule NavigationLayout

Slide 8

Slide 8 text

STATE OF THE ART

Slide 9

Slide 9 text

THE COMPONENT LEVELS

Slide 10

Slide 10 text

UI COMPONENT

Slide 11

Slide 11 text

SCREEN COMPONENT

Slide 12

Slide 12 text

APP COMPONENT

Slide 13

Slide 13 text

APP COMPONENT

Slide 14

Slide 14 text

ADAPTIVE LAYOUTS

Slide 15

Slide 15 text

WindowInset WindowManager 9:30

Slide 16

Slide 16 text

9:30 9:30 9:30 Compact Medium Expanded WINDOWSIZECLASS

Slide 17

Slide 17 text

9:30 TWOPANE LAYOUT

Slide 18

Slide 18 text

NEW SCAFFOLD COMPONENTS: LISTDETAILS AND SUPPORTING SCAFFOLDS 9:30 9:30

Slide 19

Slide 19 text

NEW SCAFFOLD COMPONENTS: NAVIGATIONSUITESCAFFOLD 9:30 9:30 9:30 9:30

Slide 20

Slide 20 text

IMPLEMENTATION IN REAL LIFE APPLICATION

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

REWORK YOUR COMPONENT ARCHITECTURE

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

:schedules :schedules :speakers :speakers

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

START WORKING ON THE LANDSCAPE MODE

Slide 27

Slide 27 text

START WORKING ON THE LANDSCAPE MODE ▸ Size variants

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

@Composable fun SmallScheduleCard( title: String, speakersUrls: ImmutableList, 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

Slide 30

Slide 30 text

@Composable fun MediumScheduleCard( title: String, speakersUrls: ImmutableList, 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

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

START WORKING ON THE LANDSCAPE MODE ▸ Size variants ▸ Adapts UI based on the window configuration

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

: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 )

Slide 35

Slide 35 text

:schedules:feature @Composable fun ScheduleGridVM( onFilterClicked: () !" Unit, onTalkClicked: (id: String) !" Unit, showFilterIcon: Boolean, modifier: Modifier = Modifier, isSmallSize: Boolean = false, viewModel: ScheduleGridViewModel = koinViewModel() )

Slide 36

Slide 36 text

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 ) }

Slide 37

Slide 37 text

USE ADAPTIVE LAYOUTS

Slide 38

Slide 38 text

USE ADAPTIVE LAYOUTS ▸ NavigationSuiteScaffold

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

@Composable fun NavigationSuiteScaffold( navigationSuiteItems: NavigationSuiteScope.() !" Unit, modifier: Modifier = Modifier, layoutType: NavigationSuiteType = NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(WindowAdaptiveInfoDefault), navigationSuiteColors: NavigationSuiteColors = NavigationSuiteDefaults.colors(), containerColor: Color = NavigationSuiteScaffoldDefaults.containerColor, contentColor: Color = NavigationSuiteScaffoldDefaults.contentColor, content: @Composable () !" Unit = {}, )

Slide 41

Slide 41 text

@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

Slide 42

Slide 42 text

USE ADAPTIVE LAYOUTS ▸ NavigationSuiteScaffold ▸ Canonical layouts

Slide 43

Slide 43 text

No content

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

@Composable fun SupportingPaneScaffold( directive: PaneScaffoldDirective, value: ThreePaneScaffoldValue, mainPane: @Composable ThreePaneScaffoldScope.() !" Unit, supportingPane: @Composable ThreePaneScaffoldScope.() !" Unit, modifier: Modifier = Modifier, extraPane: (@Composable ThreePaneScaffoldScope.() !" Unit)? = null, )

Slide 46

Slide 46 text

@Composable fun ScheduleGridAdaptive( onFilterClicked: () !" Unit, onTalkClicked: (id: String) !" Unit, showFilterIcon: Boolean, modifier: Modifier = Modifier, isSmallSize: Boolean = false, ) { val navigator = rememberSupportingPaneScaffoldNavigator() SupportingPaneScaffold( directive = navigator.scaffoldDirective, value = navigator.scaffoldValue, 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

Slide 47

Slide 47 text

@Composable fun ListDetailPaneScaffold( directive: PaneScaffoldDirective, value: ThreePaneScaffoldValue, listPane: @Composable ThreePaneScaffoldScope.() !" Unit, detailPane: @Composable ThreePaneScaffoldScope.() !" Unit, modifier: Modifier = Modifier, extraPane: (@Composable ThreePaneScaffoldScope.() !" Unit)? = null, )

Slide 48

Slide 48 text

@Composable fun SpeakerAdaptive( showBackInDetail: Boolean, onTalkClicked: (id: String) !" Unit, onLinkClicked: (url: String) !" Unit, modifier: Modifier = Modifier, state: LazyGridState = rememberLazyGridState() ) { val navigator = rememberListDetailPaneScaffoldNavigator() BackHandler(navigator.canNavigateBack()) { navigator.navigateBack() } ListDetailPaneScaffold( directive = navigator.scaffoldDirective, value = navigator.scaffoldValue, modifier = modifier, listPane = { !& list declaration }, detailPane = { !& detail declaration } ) } :speakers:feature

Slide 49

Slide 49 text

@Composable fun SpeakerAdaptive( showBackInDetail: Boolean, onTalkClicked: (id: String) !" Unit, onLinkClicked: (url: String) !" Unit, modifier: Modifier = Modifier, state: LazyGridState = rememberLazyGridState() ) { val navigator = rememberListDetailPaneScaffoldNavigator() BackHandler(navigator.canNavigateBack()) { navigator.navigateBack() } ListDetailPaneScaffold( directive = navigator.scaffoldDirective, value = navigator.scaffoldValue, modifier = modifier, listPane = { AnimatedPane { SpeakersGridVM( onSpeakerClicked = { navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, content = it) }, state = state ) } }, detailPane = { !& detail declaration } ) } :speakers:feature

Slide 50

Slide 50 text

@Composable fun SpeakerAdaptive( !$ parameters !% ) { val navigator = rememberListDetailPaneScaffoldNavigator() BackHandler(navigator.canNavigateBack()) { navigator.navigateBack() } ListDetailPaneScaffold( directive = navigator.scaffoldDirective, value = navigator.scaffoldValue, modifier = modifier, listPane = { !& list declaration }, detailPane = { navigator.currentDestination!'content!'let { AnimatedPane { SpeakerDetailVM( speakerId = it, onTalkClicked = onTalkClicked, onLinkClicked = onLinkClicked, navigationIcon = if (showBackInDetail) { @Composable { Back { if (navigator.canNavigateBack()) { navigator.navigateBack() } } } } else { null } ) } } } :speakers:feature

Slide 51

Slide 51 text

FRICTION POINTS

Slide 52

Slide 52 text

FRICTION POINTS ▸ Conflicts in Material Design specifications

Slide 53

Slide 53 text

No content

Slide 54

Slide 54 text

9:30 9:30 Compact Medium Expanded 9:30

Slide 55

Slide 55 text

16dp 16dp

Slide 56

Slide 56 text

16dp 16dp 16dp 16dp

Slide 57

Slide 57 text

No content

Slide 58

Slide 58 text

FRICTION POINTS ▸ Conflict in Material Design specifications ▸ Floating Action Button

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

No content

Slide 62

Slide 62 text

9:30 ?

Slide 63

Slide 63 text

No content

Slide 64

Slide 64 text

FRICTION POINTS ▸ Conflict in Material Design specifications ▸ Floating Action Button ▸ WindowSizeClass not available for sub-components

Slide 65

Slide 65 text

No content

Slide 66

Slide 66 text

CONCLUSION

Slide 67

Slide 67 text

First presentation February 22th 1.0.0 A lpha 07 February 21th 1.0.0 Beta 01 M ay 1st 1.0.0 rc 01 A ugust 21th 1.0.0 Septem ber 4th

Slide 68

Slide 68 text

No content

Slide 69

Slide 69 text

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 ▸ Confily https://github.com/GerardPaligot/confily

Slide 70

Slide 70 text

THANK YOU! @GerardPaligot @Smoochibi