List-detail is a UI pattern that consists of a dual-pane layout where one pane presents a list of items and another pane displays the details of items selected from the list.
The pattern is particularly useful for applications that provide in-depth information about elements of large collections, for example, an email client that has a list of emails and the detailed content of each email message. List-detail can also be used for less critical paths such as dividing app preferences into a list of categories with the preferences for each category in the detail pane.
Implement UI pattern with ListDetailPaneScaffold
ListDetailPaneScaffold
is a composable that simplifies the implementation of
the list-detail pattern in your app. A list-detail scaffold can consist of up to
three panes: a list pane, a detail pane, and an optional extra pane. The
scaffold handles screen space calculations. When sufficient screen size is
available, the detail pane is displayed alongside the list pane. On small screen
sizes, the scaffold automatically switches to displaying either the list or
detail pane full screen.
Declare dependencies
ListDetailPaneScaffold
is part of the Material 3 adaptive layout
library.
Add the following three, related dependencies to the build.gradle
file of your
app or module:
Kotlin
implementation("androidx.compose.material3.adaptive:adaptive") implementation("androidx.compose.material3.adaptive:adaptive-layout") implementation("androidx.compose.material3.adaptive:adaptive-navigation")
Groovy
implementation 'androidx.compose.material3.adaptive:adaptive' implementation 'androidx.compose.material3.adaptive:adaptive-layout' implementation 'androidx.compose.material3.adaptive:adaptive-navigation'
- adaptive — Low-level building blocks such as
HingeInfo
andPosture
- adaptive-layout — Adaptive layouts such as
ListDetailPaneScaffold
andSupportingPaneScaffold
- adaptive-navigation — Composables for navigating within and between panes
Basic usage
Implement ListDetailPaneScaffold
as follows:
Use a class that represents the content to be selected. This class should be
Parcelable
to support saving and restoring the selected list item. Use the kotlin-parcelize plugin to generate the code for you.@Parcelize class MyItem(val id: Int) : Parcelable
Create a
ThreePaneScaffoldNavigator
withrememberListDetailPaneScaffoldNavigator
and add aBackHandler
. This navigator is used to move between the list, detail, and extra panes. By declaring a generic type, the navigator also tracks the state of the scaffold (that is, whichMyItem
is being displayed). Since this type is parcelable, the state can be saved and restored by the navigator to automatically handle configuration changes. TheBackHandler
provides support for navigating back using the system back gesture or button. The expected behavior of the back button for aListDetailPaneScaffold
depends on the window size and current scaffold value. If theListDetailPaneScaffold
can support going back with the current state, thencanNavigateBack()
istrue
, enabling theBackHandler
.val navigator = rememberListDetailPaneScaffoldNavigator<MyItem>() BackHandler(navigator.canNavigateBack()) { navigator.navigateBack() }
Pass the
scaffoldState
from thenavigator
to theListDetailPaneScaffold
composable.ListDetailPaneScaffold( directive = navigator.scaffoldDirective, value = navigator.scaffoldValue, // ... )
Supply your list pane implementation to the
ListDetailPaneScaffold
. UseAnimatedPane
to apply the default pane animations during navigation. Then useThreePaneScaffoldNavigator
to navigate to the detail pane,ListDetailPaneScaffoldRole.Detail
, and display the passed item.ListDetailPaneScaffold( directive = navigator.scaffoldDirective, value = navigator.scaffoldValue, listPane = { AnimatedPane { MyList( onItemClick = { item -> // Navigate to the detail pane with the passed item navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, item) } ) } }, // ... )
Include your detail pane implementation in
ListDetailPaneScaffold
. When navigation has completed,currentDestination
contains the pane your app has navigated to, including the content displayed in the pane. Thecontent
property is the same type specified in the original remember call (MyItem
in this example), so you can also access the property for any data that you need to display.ListDetailPaneScaffold( directive = navigator.scaffoldDirective, value = navigator.scaffoldValue, listPane = // ... detailPane = { AnimatedPane { navigator.currentDestination?.content?.let { MyDetails(it) } } }, )
After you implement the above steps, your code should look similar to this:
val navigator = rememberListDetailPaneScaffoldNavigator<MyItem>() BackHandler(navigator.canNavigateBack()) { navigator.navigateBack() } ListDetailPaneScaffold( directive = navigator.scaffoldDirective, value = navigator.scaffoldValue, listPane = { AnimatedPane { MyList( onItemClick = { item -> // Navigate to the detail pane with the passed item navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, item) }, ) } }, detailPane = { AnimatedPane { // Show the detail pane content if selected item is available navigator.currentDestination?.content?.let { MyDetails(it) } } }, )