Blog Infos
Author
Published
Topics
, , , ,
Published

This year’s Google I/O was packed with exciting announcements, not just in the realm of AI (although that was certainly a highlight ). For me, a key takeaway was the focus on advancements in Jetpack Compose for building adaptive layouts. As Android extends its reach beyond smartphones to tablets, foldables, and large screens, creating apps that adapt to various form factors is more important than ever.

I previously explored responsive layouts using window size classes in my previous article. However, exciting new developments in Jetpack Compose have prompted me to revisit the topic. Not only is there a new implementation of WindowSizeClass that simplifies usage, but there are also new composable functions that streamline common layout behaviors, eliminating the need for custom functions.

Since we have a lot of ground to cover, let’s dive straight into the first key aspect: the new implementation of WindowSizeClass. To provide context, I’ll begin by migrating the implementation from my previous article, take a look at it if you want.

Migration of WindowSizeClass

Let’s begin by updating our Gradle file. We’ll remove the old dependency and add the new one for the revamped implementation.

[versions]
adaptive = "1.0.0-beta01"
...
[libraries]

-- androidx-material3-windowSizeClass = { group = "androidx.compose.material3", name = "material3-window-size-class" }
androidx-adaptive = { module = "androidx.compose.material3.adaptive:adaptive", version.ref = "adaptive" }
...

With the Gradle dependency updated, let’s migrate the code sections that previously relied on the old WindowSizeClass implementation. For instance, if you had code that dynamically determined the number of columns based on window size, we can update it like this:

val windowWidthSize = currentWindowAdaptiveInfo().windowSizeClass.windowWidthSizeClass  
 
val columns = when (windowWidthSize) {
       WindowWidthSizeClass.COMPACT -> 1
       WindowWidthSizeClass.MEDIUM -> 2
       else -> 3
}

As you can see the update goes beyond just function changes! A key improvement is the ability to retrieve the windowSizeClass directly within any composable function, eliminating the need to access it through the activity. This means you can finally ditch the practice of passing the window size class as a parameter throughout your app! This is a significant step forward for cleaner code.

NavigationSuiteScaffold

With the windowSizeClass migration covered, let’s delve into the exciting new composable functions. First up, we have NavigationSuiteScaffold. This essential composable eliminates the need for custom logic when switching between bottom navigation bars, navigation rails, and drawers based on window size.

In my previous article, I explored building a custom solution for switching navigation elements. Now, let’s see how NavigationSuiteScaffold simplifies this process. Here’s how you can use this new function to achieve the same outcome:

... 
NavigationSuiteScaffold(
        modifier = Modifier, 
        navigationSuiteItems = {

            bottomNavigationItems.forEach { bottomBarElement ->

                val selected =
                    currentScreen.instanceOf(bottomBarElement.screen::class)

                item(
                    icon = bottomBarElement.icon,
                    selected = selected,
                    alwaysShowLabel = true,
                    label = {
                        Text(
                            text = stringResource(id = bottomBarElement.title),
                            style = MaterialTheme.typography.labelMedium.copy(
                                textAlign = TextAlign.Center,
                                fontWeight = FontWeight.Normal
                            ),
                            maxLines = 1
                        )
                    },
                    onClick = {
                        if (!selected) {
                                NavigationEvent.OnNavigateBottomBar(
                                    bottomBarElement.screen
                                )
                        }
                    }
                )
            }
        }

    ) {
        Scaffold() { innerPadding ->
            MainNavHost(
                modifier = Modifier.padding(innerPadding),
            )
        }
    }

This single implementation automatically adapts its behavior to deliver the appropriate navigation experience based on the current window size. It provides the appropriate navigation experience, switching between elements like a bottom bar on small screens and a navigation rail on larger screens.

However, for those who prefer a more customized approach, NavigationSuiteScaffold offers flexibility through NavigationSuiteType. You can seamlessly integrate custom behaviors within the scaffold, allowing you to use elements like the navigation rail even in landscape mode on smartphones.

... 
val adaptiveInfo = currentWindowAdaptiveInfo()
val customNavSuiteType = with(adaptiveInfo) {
            if (windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.EXPANDED) {
                NavigationSuiteType.NavigationRail
            } else {
                NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(adaptiveInfo)
            }
}

NavigationSuiteScaffold(
        modifier = Modifier,
        layoutType = customNavSuiteType,
        navigationSuiteItems = {
...

The same principle applies if you want to hide navigation entirely in specific scenarios.

... 
val adaptiveInfo = currentWindowAdaptiveInfo()
    val customNavSuiteType = with(adaptiveInfo) {
         when {
            !shouldShowBottomBar -> {
                NavigationSuiteType.None
            }
            windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.EXPANDED -> {
                NavigationSuiteType.NavigationRail
            }
            else -> {
                NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(adaptiveInfo)
            }
        }
    }
...

The result will be something like this:

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

Jobs

ListDetailPaneScaffold

Ok, let’s explore another powerful composable: ListDetailPaneScaffold. This function is ideal for scenarios where you want to display two screens (or panes, as the name suggests) side-by-side on larger window sizes. One of its key strengths is the automatic handling of internal navigation, whether you choose to show a single pane or utilize the dual-pane layout. This not only simplifies development but also ensures a smooth user experience regardless of the window size.

[versions]
material3AdaptiveNavigationSuiteAndroid = "1.3.0-beta01"
...
[libraries]

androidx-material3-adaptive-navigation-suite-android = { group = "androidx.compose.material3", name = "material3-adaptive-navigation-suite-android", version.ref = "material3AdaptiveNavigationSuiteAndroid" }
...

With the Gradle dependency we can start to update the code.

 val navigator = rememberListDetailPaneScaffoldNavigator<String>()

    BackHandler(navigator.canNavigateBack()) {
        navigator.navigateBack()
    }

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        listPane = {
            AnimatedPane {
                HomeScreen(
                    onClickOnItem = {
                        navigator.navigateTo(
                            ListDetailPaneScaffoldRole.Detail,
                            it
                        )
                    }
                )
            }
        },
        detailPane = {
            AnimatedPane {
                navigator.currentDestination?.content?.let {
                    ZoomBookInitScreen(book = it.id)
                }
            }
        },
    )

As the name suggests, the navigator is responsible for managing navigation within the panes. This includes navigating to the detail pane, as well as handling back navigation when you’re in single-pane mode. It also include the data object passed to the detail pane. Notably, you can even share custom objects as long as they are Parcelable.

We can obviusly use the ListDetailPaneScaffold with WindowSizeClass, to tailor behavior based on the current window size. For instance (as you’ll see in the screenshots), you can conditionally display elements like a back arrow only in single-pane mode.

val windowWidthSize = currentWindowAdaptiveInfo().windowSizeClass.windowWidthSizeClass
val backVisible = when (windowWidthSize) {
        WindowWidthSizeClass.EXPANDED -> false
        else -> true
    }

This level of control ensures a refined user experience across all different device sizes.

SupportingPaneScaffold

To complete our exploration of new adaptive layout functions, let’s briefly discuss SupportingPaneScaffold. This composable shares similarities with ListDetailPaneScaffold in its core functionality: managing navigation and displaying content within panes. However, SupportingPaneScaffold is tailored for scenarios where you have a primary content pane alongside a smaller, “supporting” pane on the right. This makes it ideal for situations where the secondary content provides additional information or complements the main content, but doesn’t require equal screen space.

If you’d like to implement SupportingPaneScaffold in your project, you can leverage the knowledge gained from ListDetailPaneScaffold as a foundation. For a deeper dive I leave here the official Jetpack Compose documentation.

Conclusions

As you’ve seen throughout this article, Google I/O 2024 brought exciting advancements in building adaptive layouts with Jetpack Compose. The new WindowSizeClass implementation simplifies access and usage, while powerful composable functions like NavigationSuiteScaffoldListDetailPaneScaffold, and SupportingPaneScaffold offer a streamlined approach to handling navigation and content presentation across various screen sizes and form factors.

These new features empower us to create truly responsive and user-friendly experiences for our Android applications. By embracing these tools and following best practices for adaptive layouts, you can ensure your apps gracefully adapt to the evolving Android ecosystem, providing exceptional experiences for users on all devices.

Feel free to share your comments, or if you prefer, you can reach out to me on LinkedIn.

Have a great day!

This article is previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
In the world of Jetpack Compose, where designing reusable and customizable UI components is…
READ MORE
blog
Hi, today I come to you with a quick tip on how to update…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu