Cómo compilar apps adaptables con Jetpack Compose

1. Introducción

En este codelab, aprenderás a compilar apps adaptables para teléfonos, tablets y plegables, y también la forma en la que estas mejoran la accesibilidad con Jetpack Compose. También conocerás las prácticas recomendadas para usar temas y componentes de Material 3.

Antes de comenzar, es importante comprender qué entendemos por adaptabilidad.

Adaptabilidad

La IU de tu app debe ser responsiva para los diferentes tamaños de ventana, orientaciones y factores de forma. Un diseño adaptable cambia en función del espacio de pantalla disponible. Estos cambios varían desde simples ajustes de diseño para llenar espacio, con la elección de los estilos de navegación respectivos, hasta cambios completos de diseño para aprovechar espacio adicional.

Para obtener más información, consulta Diseño adaptable.

En este codelab, explorarás cómo usar la adaptabilidad y pensar en ella cuando uses Jetpack Compose. Compilarás una aplicación, llamada Reply, que te mostrará cómo implementar la adaptabilidad para todo tipo de pantallas, y cómo la adaptabilidad y la accesibilidad funcionan en conjunto para brindar a los usuarios una experiencia óptima.

Qué aprenderás

  • Cómo diseñar tu app para que se oriente a todos los tamaños de ventana con Jetpack Compose
  • Cómo segmentar tu app para diferentes dispositivos plegables
  • Cómo usar diferentes tipos de navegación para lograr una mejor accesibilidad
  • Cómo usar los componentes de Material 3 para brindar la mejor experiencia en todos los tamaños de ventana

Requisitos

En este codelab, usarás el emulador de tamaño variable, que te permite alternar entre diferentes tipos de dispositivos y tamaños de ventana.

Emulador de tamaño variable con opciones de teléfono, dispositivo desplegado, tablet y computadora de escritorio.

Si no conoces Compose, antes de completar este codelab, considera realizar el codelab de los principios básicos de Jetpack Compose.

Qué compilarás

  • Una app cliente de correo electrónico interactiva llamada Reply, que usa prácticas recomendadas para diseños adaptables, diferentes navegaciones de Material y un uso óptimo del espacio de pantalla

Demostración de la compatibilidad con varios dispositivos que obtendrás en este codelab

2. Prepárate

Para obtener el código necesario para este codelab, clona el repositorio de GitHub desde la línea de comandos:

git clone https://github.com/android/codelab-android-compose.git
cd codelab-android-compose/AdaptiveUiCodelab

También tienes la opción de descargar el repositorio como archivo ZIP:

Te recomendamos que comiences con el código de la rama main y sigas el codelab paso a paso a tu propio ritmo.

Abre el proyecto en Android Studio

  1. En la ventana Welcome to Android Studio, selecciona c01826594f360d94.pngOpen an Existing Project.
  2. Selecciona la carpeta <Download Location>/AdaptiveUiCodelab (asegúrate de seleccionar el directorio AdaptiveUiCodelab que contiene build.gradle).
  3. Cuando Android Studio haya importado el proyecto, prueba si puedes ejecutar la rama main.

Cómo explorar el código de inicio

El código de la rama main contiene el paquete ui. Trabajarás con los siguientes archivos en ese paquete:

  • MainActivity.kt: Es la actividad de punto de entrada donde inicias tu app.
  • ReplyApp.kt: Contiene elementos componibles de la IU de la pantalla principal.
  • ReplyHomeViewModel.kt: Proporciona los datos y el estado de la IU para el contenido de la app.
  • ReplyListContent.kt: Contiene elementos componibles para proporcionar pantallas de detalles y listas.

Si ejecutas esta app en un emulador de tamaño variable y pruebas diferentes tipos de dispositivos, como un teléfono o una tablet, la IU simplemente se expande al espacio determinado en lugar de aprovechar el espacio de pantalla o proporcionar una ergonomía de accesibilidad.

Pantalla inicial del teléfono

Vista extendida inicial de la tablet

La actualizarás para aprovechar el espacio de la pantalla, aumentar la usabilidad y mejorar la experiencia general del usuario.

3. Cómo lograr que las apps sean adaptables

En esta sección, se explica qué significa hacer que las apps sean adaptables y qué componentes proporciona Material 3 de modo que eso resulte más fácil. También se incluyen los tipos de pantallas y estados objetivo, incluidos los teléfonos, las tablets, las tablets grandes y los plegables.

Para comenzar, analizaremos los aspectos básicos de los tamaños de ventana, las posiciones de plegado y los diferentes tipos de opciones de navegación. Luego, puedes usar estas APIs en tu app para hacerla más adaptable.

Tamaños de las ventanas

Los dispositivos Android pueden tener diferentes formas y tamaños, desde teléfonos y plegables hasta tablets y dispositivos ChromeOS. Para admitir tantos tamaños de ventana como sea posible, tu IU debe ser responsiva y adaptable. Para ayudarte a encontrar el umbral correcto en el que se debe cambiar la IU de tu app, definimos valores de puntos de interrupción que ayudan a clasificar los dispositivos en clases de tamaño predefinidas (compactas, medianas y expandidas), llamadas clases de tamaño de ventana. Estos son un conjunto de puntos de interrupción de viewports bien definidos que te ayudan a diseñar, desarrollar y probar diseños de aplicaciones responsivos y adaptables.

Las categorías se eligieron específicamente a los efectos de equilibrar la simplicidad del diseño, con la flexibilidad para optimizar tu app en casos únicos. El espacio de pantalla disponible para la app siempre determina la clase de tamaño de ventana, que puede no ser la pantalla física completa debido a la realización de varias tareas a la vez o a otras segmentaciones.

WindowWidthSizeClass para el ancho compacto, medio y expandido.

WindowHeightSizeClass para una altura compacta, mediana y expandida.

El ancho y la altura se clasifican por separado, por lo que en cualquier momento, tu app tiene dos clases de tamaño de ventana: una para el ancho y otra para la altura. El ancho disponible suele ser más importante que la altura disponible debido a la ubicuidad del desplazamiento vertical, por lo que, en este caso, también usarás clases de tamaño de ancho de ventana.

Estados de plegado

Los dispositivos plegables presentan aún más situaciones a las que tu app puede adaptarse debido a sus diferentes tamaños y la presencia de bisagras. Las bisagras pueden ocultar parte de la pantalla, lo que hace que ese área no sea adecuada para mostrar contenido. También pueden separarse, lo que significa que hay dos pantallas físicas separadas cuando el dispositivo está desplegado.

Posiciones plegables, plana y semiabierta

Además, el usuario podría estar mirando la pantalla interior mientras la bisagra está parcialmente abierta, lo que genera diferentes posiciones físicas según la orientación del pliegue: posición de mesa (pliegue horizontal, que se muestra a la derecha en la imagen anterior) y posición de libro (pliegue vertical).

Obtén más información sobre las posturas y bisagras de plegado.

Todo esto es algo que debes tener en cuenta cuando implementes diseños adaptables que admitan dispositivos plegables.

Cómo obtener información adaptable

La biblioteca adaptive de Material3 proporciona un acceso conveniente a la información sobre la ventana en la que se ejecuta tu app.

  1. Agrega entradas para este artefacto y su versión al archivo del catálogo de versiones:

gradle/libs.versions.toml

[versions]
material3Adaptive = "1.0.0"

[libraries]
androidx-material3-adaptive = { module = "androidx.compose.material3.adaptive:adaptive", version.ref = "material3Adaptive" }
  1. En el archivo de compilación del módulo de la app, agrega la nueva dependencia de biblioteca y, luego, realiza una sincronización de Gradle:

app/build.gradle.kts

dependencies {

    implementation(libs.androidx.material3.adaptive)
}

Ahora, en cualquier alcance componible, puedes usar currentWindowAdaptiveInfo() para obtener un objeto WindowAdaptiveInfo que contenga información como la clase de tamaño de ventana actual y si el dispositivo está en una posición plegable, como la de mesa.

Puedes probarlo ahora en MainActivity.

  1. En onCreate() dentro del bloque ReplyTheme, obtén la información adaptable de la ventana y muestra las clases de tamaño en un elemento componible Text. Puedes agregar esto después del elemento ReplyApp():

MainActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContent {
        ReplyTheme {
            val uiState by viewModel.uiState.collectAsStateWithLifecycle()
            ReplyApp(
                replyHomeUIState = uiState,
                onEmailClick = viewModel::setSelectedEmail
            )

            val adaptiveInfo = currentWindowAdaptiveInfo()
            val sizeClassText =
                "${adaptiveInfo.windowSizeClass.windowWidthSizeClass}\n" +
                "${adaptiveInfo.windowSizeClass.windowHeightSizeClass}"
            Text(
                text = sizeClassText,
                color = Color.Magenta,
                modifier = Modifier.padding(
                    WindowInsets.safeDrawing.asPaddingValues()
                )
            )
        }
    }
}

Cuando se ejecute la app, se mostrarán las clases de tamaño de ventana impresas en el contenido de la app. No dudes en explorar qué más se proporciona en la información de adaptación de la ventana. Luego, puedes quitar este Text, ya que cubre el contenido de la app y no será necesario para los próximos pasos.

4. Navegación dinámica

Ahora, adaptarás la navegación de la app a medida que cambie el estado y el tamaño del dispositivo para que sea más fácil de usar.

Cuando los usuarios sostienen un teléfono, sus dedos suelen estar en la parte inferior de la pantalla. Cuando los usuarios sostienen un dispositivo plegable o una tablet abiertos, suelen tener los dedos cerca de los lados. Tus usuarios deben poder navegar o iniciar una interacción con una app sin requerir posiciones extremas de las manos ni cambiar la ubicación.

A medida que diseñes tu app y decidas dónde colocar los elementos interactivos de la IU en el diseño, ten en cuenta las implicaciones ergonómicas de las diferentes regiones de la pantalla.

  • ¿Qué áreas son cómodas de alcanzar mientras se sujeta el dispositivo?
  • ¿A qué áreas solo se puede llegar extendiendo los dedos, lo que puede ser inconveniente?
  • ¿Qué áreas son difíciles de alcanzar o están lejos de donde el usuario sostiene el dispositivo?

La navegación es lo primero con lo que interactúan los usuarios y contiene acciones de alta importancia relacionadas con recorridos del usuario fundamentales, por lo que debe colocarse en áreas de fácil acceso. La biblioteca adaptable de Material proporciona varios componentes que te ayudan a implementar la navegación, según la clase de tamaño de ventana del dispositivo.

Navegación inferior

La navegación inferior es perfecta para tamaños compactos, ya que naturalmente sostenemos el dispositivo de modo que nuestro dedo pulgar pueda llegar con facilidad a todos los puntos táctiles de navegación inferior. Úsala siempre que tengas un tamaño de dispositivo compacto o plegable en ese estado.

Barra de navegación inferior con elementos

Para un tamaño de ventana de ancho mediano, el riel de navegación es ideal para la accesibilidad, ya que nuestro dedo pulgar se ubica naturalmente a lo largo del costado del dispositivo. También puedes combinar un riel de navegación con un panel lateral de navegación para mostrar más información.

Riel de navegación con elementos

El panel lateral de navegación proporciona una forma fácil de ver información detallada de las pestañas de navegación, y es fácil de acceder cuando usas tablets o dispositivos más grandes. Hay dos tipos de paneles laterales de navegación disponibles: uno modal y uno permanente.

Panel lateral de navegación modal

Puedes usar un panel lateral de navegación modal para teléfonos y tablets de tamaño compacto a mediano, ya que se puede ocultar o expandir como una superposición en el contenido. A veces, se puede combinar con un riel de navegación.

Panel lateral de navegación modal con elementos

Panel lateral de navegación permanente

Puedes usar un panel lateral de navegación permanente para la navegación fija en tablets, Chromebooks y computadoras de escritorio grandes.

Panel lateral de navegación permanente con elementos

Cómo implementar la navegación dinámica

Ahora, cambiarás entre los diferentes tipos de navegación a medida que cambien el estado y el tamaño del dispositivo.

Actualmente, la app siempre muestra un NavigationBar debajo del contenido de la pantalla, independientemente del estado del dispositivo. En su lugar, puedes usar el componente NavigationSuiteScaffold de Material para cambiar automáticamente entre los diferentes componentes de navegación según información como la clase de tamaño de ventana actual.

  1. Para obtener este componente, agrega la dependencia de Gradle. Para ello, actualiza el catálogo de versiones y la secuencia de comandos de compilación de la app y, luego, realiza una sincronización de Gradle:

gradle/libs.versions.toml

[versions]
material3AdaptiveNavSuite = "1.3.0"

[libraries]
androidx-material3-adaptive-navigation-suite = { module = "androidx.compose.material3:material3-adaptive-navigation-suite", version.ref = "material3AdaptiveNavSuite" }

app/build.gradle.kts

dependencies {

    implementation(libs.androidx.material3.adaptive.navigation.suite)
}
  1. Busca la función de componibilidad ReplyNavigationWrapper() en ReplyApp.kt y reemplaza Column y su contenido por un NavigationSuiteScaffold:

ReplyApp.kt

@Composable
private fun ReplyNavigationWrapperUI(
    content: @Composable () -> Unit = {}
) {
    var selectedDestination: ReplyDestination by remember {
        mutableStateOf(ReplyDestination.Inbox)
    }

    NavigationSuiteScaffold(
        navigationSuiteItems = {
            ReplyDestination.entries.forEach {
                item(
                    selected = it == selectedDestination,
                    onClick = { /*TODO update selection*/ },
                    icon = {
                        Icon(
                            imageVector = it.icon,
                            contentDescription = stringResource(it.labelRes)
                        )
                    },
                    label = {
                        Text(text = stringResource(it.labelRes))
                    },
                )
            }
        }
    ) {
        content()
    }
}

El argumento navigationSuiteItems es un bloque que te permite agregar elementos con la función item(), de manera similar a como se agregan elementos en un LazyColumn. Dentro de la expresión lambda final, este código llama a content() pasado como argumento a ReplyNavigationWrapperUI().

Ejecuta la app en el emulador y prueba cambiar los tamaños entre teléfono, plegable y tablet. Verás que la barra de navegación cambia a un riel de navegación y viceversa.

En ventanas muy anchas, como en una tablet en orientación horizontal, es posible que quieras mostrar el panel lateral de navegación permanente. NavigationSuiteScaffold admite mostrar un panel lateral permanente, aunque no se muestra en ninguno de los valores actuales de WindowWidthSizeClass. Sin embargo, puedes hacer que lo haga con un pequeño cambio.

  1. Agrega el siguiente código justo antes de la llamada a NavigationSuiteScaffold:

ReplyApp.kt

@Composable
private fun ReplyNavigationWrapperUI(
    content: @Composable () -> Unit = {}
) {
    var selectedDestination: ReplyDestination by remember {
        mutableStateOf(ReplyDestination.Inbox)
    }

    val windowSize = with(LocalDensity.current) {
        currentWindowSize().toSize().toDpSize()
    }
    val layoutType = if (windowSize.width >= 1200.dp) {
        NavigationSuiteType.NavigationDrawer
    } else {
        NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(
            currentWindowAdaptiveInfo()
        )
    }

    NavigationSuiteScaffold(
        layoutType = layoutType,
        ...
    ) {
        content()
    }
}

Este código primero obtiene el tamaño de la ventana y lo convierte en unidades de DP con currentWindowSize() y LocalDensity.current, y luego compara el ancho de la ventana para decidir el tipo de diseño de la IU de navegación. Si el ancho de la ventana es de al menos 1200.dp, usa NavigationSuiteType.NavigationDrawer. De lo contrario, se utiliza el cálculo predeterminado.

Cuando vuelvas a ejecutar la app en tu emulador de tamaño variable y pruebes diferentes tipos, observa que cada vez que cambia la configuración de la pantalla o despliegas un dispositivo plegable, la navegación cambia al tipo adecuado para ese tamaño.

Se muestran los cambios de adaptabilidad para diferentes tamaños de dispositivos.

¡Felicitaciones! Aprendiste sobre diferentes tipos de navegación para admitir diferentes tipos de tamaños de ventana y estados.

En la siguiente sección, explorarás cómo aprovechar el área restante de la pantalla en lugar de estirar un mismo elemento de la lista de borde a borde.

5. Uso del espacio de la pantalla

No importa si ejecutas la app en una tablet pequeña, un dispositivo desplegado o una tablet grande, la pantalla se estirará para llenar el espacio restante. Asegúrate de que puedas aprovechar ese espacio en pantalla para mostrar más información, como esta app, que muestra el correo electrónico y las conversaciones a los usuarios en la misma página.

Material 3 define tres diseños canónicos, cada uno con configuraciones para clases de tamaño de ventana compacta, mediana y expandida. El diseño canónico de List Detail es perfecto para este caso de uso y está disponible en Compose como ListDetailPaneScaffold.

  1. Para obtener este componente, agrega las siguientes dependencias y realiza una sincronización de Gradle:

gradle/libs.versions.toml

[libraries]
androidx-material3-adaptive-layout = { module = "androidx.compose.material3.adaptive:adaptive-layout", version.ref = "material3Adaptive" }
androidx-material3-adaptive-navigation = { module = "androidx.compose.material3.adaptive:adaptive-navigation", version.ref = "material3Adaptive" }

app/build.gradle.kts

dependencies {

    implementation(libs.androidx.material3.adaptive.layout)
    implementation(libs.androidx.material3.adaptive.navigation)
}
  1. Busca la función de componibilidad ReplyAppContent() en ReplyApp.kt, que actualmente solo muestra el panel de la lista llamando a ReplyListPane(). Para reemplazar esta implementación por ListDetailPaneScaffold, inserta el siguiente código. Dado que esta es una API experimental, también agregarás la anotación @OptIn a la función ReplyAppContent():

ReplyApp.kt

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ReplyAppContent(
    replyHomeUIState: ReplyHomeUIState,
    onEmailClick: (Email) -> Unit,
) {
    val navigator = rememberListDetailPaneScaffoldNavigator<Long>()

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        listPane = {
            ReplyListPane(replyHomeUIState, onEmailClick)
        },
        detailPane = {
            ReplyDetailPane(replyHomeUIState.emails.first())
        }
    )
}

Primero, este código crea un navegador con rememberListDetailPaneNavigator(). El navegador proporciona cierto control sobre qué panel se muestra y qué contenido debe representarse en ese panel, lo que se demostrará más adelante.

ListDetailPaneScaffold mostrará dos paneles cuando se expanda la clase de tamaño de ancho de ventana. De lo contrario, se mostrará un panel o el otro según los valores proporcionados para dos parámetros: la directiva de andamiaje y el valor del andamiaje. Para obtener el comportamiento predeterminado, este código usa la directiva de andamiaje y el valor de andamiaje que proporciona el navegador.

Los parámetros obligatorios restantes son lambdas componibles para los paneles. ReplyListPane() y ReplyDetailPane() (que se encuentran en ReplyListContent.kt) se usan para completar los roles de los paneles de lista y de detalles, respectivamente. ReplyDetailPane() espera un argumento de correo electrónico, por lo que, por ahora, este código usa el primer correo electrónico de la lista de correos electrónicos en ReplyHomeUIState.

Ejecuta la app y cambia la vista del emulador a plegable o tablet (es posible que también debas cambiar la orientación) para ver el diseño de dos paneles. ¡Esto ya se ve mucho mejor que antes!

Ahora, analicemos algunos de los comportamientos deseados de esta pantalla. Cuando el usuario presiona un correo electrónico en el panel de la lista, este debería mostrarse en el panel de detalles junto con todas las respuestas. Actualmente, la app no lleva un registro de qué correo electrónico se seleccionó, y presionar un elemento no hace nada. El mejor lugar para mantener esta información es con el resto del estado de la IU en ReplyHomeUIState.

  1. Abre ReplyHomeViewModel.kt y busca la clase de datos ReplyHomeUIState. Agrega una propiedad para el correo electrónico seleccionado, con un valor predeterminado de null:

ReplyHomeViewModel.kt

data class ReplyHomeUIState(
    val emails : List<Email> = emptyList(),
    val selectedEmail: Email? = null,
    val loading: Boolean = false,
    val error: String? = null
)
  1. En el mismo archivo, ReplyHomeViewModel tiene una función setSelectedEmail() a la que se llama cuando el usuario presiona un elemento de la lista. Modifica esta función para copiar el estado de la IU y registrar el correo electrónico seleccionado:

ReplyHomeViewModel.kt

fun setSelectedEmail(email: Email) {
    _uiState.update {
        it.copy(selectedEmail = email)
    }
}

Algo que se debe tener en cuenta es lo que sucede antes de que el usuario presione cualquier elemento y el correo electrónico seleccionado sea null. ¿Qué debería mostrarse en el panel de detalles? Existen varias formas de manejar este caso, como mostrar el primer elemento de la lista de forma predeterminada.

  1. En el mismo archivo, modifica la función observeEmails(). Cuando se cargue la lista de correos electrónicos, si el estado anterior de la IU no tenía un correo electrónico seleccionado, configúralo en el primer elemento:

ReplyHomeViewModel.kt

private fun observeEmails() {
    viewModelScope.launch {
        emailsRepository.getAllEmails()
            .catch { ex ->
                _uiState.value = ReplyHomeUIState(error = ex.message)
            }
            .collect { emails ->
                val currentSelection = _uiState.value.selectedEmail
                _uiState.value = ReplyHomeUIState(
                    emails = emails,
                    selectedEmail = currentSelection ?: emails.first()
                )
            }
    }
}
  1. Regresa a ReplyApp.kt y usa el correo electrónico seleccionado, si está disponible, para propagar el contenido del panel de detalles:

ReplyApp.kt

ListDetailPaneScaffold(
    // ...
    detailPane = {
        if (replyHomeUIState.selectedEmail != null) {
            ReplyDetailPane(replyHomeUIState.selectedEmail)
        }
    }
)

Vuelve a ejecutar la app, cambia el emulador al tamaño de tablet y observa que, si presionas un elemento de la lista, se actualiza el contenido del panel de detalles.

Funciona muy bien cuando ambos paneles son visibles, pero cuando la ventana solo tiene espacio para mostrar un panel, parece que no sucede nada cuando presionas un elemento. Intenta cambiar la vista del emulador a un teléfono o dispositivo plegable en orientación vertical, y observa que solo se ve el panel de lista incluso después de presionar un elemento. Esto se debe a que, aunque se actualice el correo electrónico seleccionado, ListDetailPaneScaffold mantiene el enfoque en el panel de la lista en estas configuraciones.

  1. Para corregirlo, inserta el siguiente código como la lambda que se pasa a ReplyListPane:

ReplyApp.kt

ListDetailPaneScaffold(
    // ...
    listPane = {
        ReplyListPane(
            replyHomeUIState = replyHomeUIState,
            onEmailClick = { email ->
                onEmailClick(email)
                navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, email.id)
            }
        )
    },
    // ...
)

Esta lambda usa el navegador que se creó antes para agregar un comportamiento adicional cuando se hace clic en un elemento. Llamará a la lambda original pasada a esta función y, luego, también llamará a navigator.navigateTo() para especificar qué panel se debe mostrar. Cada panel del andamiaje tiene un rol asociado y, para el panel de detalles, es ListDetailPaneScaffoldRole.Detail. En ventanas más pequeñas, esto dará la apariencia de que la app navegó hacia adelante.

La app también debe controlar lo que sucede cuando el usuario presiona el botón Atrás desde el panel de detalles, y este comportamiento será diferente según si hay un panel o dos visibles.

  1. Agrega el siguiente código para admitir la navegación hacia atrás.

ReplyApp.kt

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ReplyAppContent(
    replyHomeUIState: ReplyHomeUIState,
    onEmailClick: (Email) -> Unit,
) {
    val navigator = rememberListDetailPaneScaffoldNavigator<Long>()

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

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        listPane = {
            AnimatedPane {
                ReplyListPane(
                    replyHomeUIState = replyHomeUIState,
                    onEmailClick = { email ->
                        onEmailClick(email)
                        navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, email.id)
                    }
                )
            }
        },
        detailPane = {
            AnimatedPane {
                if (replyHomeUIState.selectedEmail != null) {
                    ReplyDetailPane(replyHomeUIState.selectedEmail)
                }
            }
        }
    )
}

El navegador conoce el estado completo de ListDetailPaneScaffold, si es posible la navegación hacia atrás y qué hacer en todas estas situaciones. Este código crea un BackHandler que se habilita siempre que el navegador puede navegar hacia atrás y, dentro de la lambda, llama a navigateBack(). Además, para que la transición entre los paneles sea mucho más fluida, cada panel se une en un elemento componible AnimatedPane().

Vuelve a ejecutar la app en un emulador de tamaño variable para todos los diferentes tipos de dispositivos y observa que cada vez que cambia la configuración de la pantalla o se despliega un dispositivo plegable, la navegación y el contenido de la pantalla cambian de forma dinámica en respuesta a los cambios de estado del dispositivo. También prueba presionar los correos electrónicos en el panel de la lista y observa cómo se comporta el diseño en diferentes pantallas, mostrando ambos paneles en paralelo o animando entre ellos sin problemas.

Se muestran los cambios de adaptabilidad para diferentes tamaños de dispositivos.

Felicitaciones. Lograste que tu app se adapte correctamente a todo tipo de estados y tamaños de dispositivos. Puedes ejecutar la app en dispositivos plegables, tablets y otros dispositivos móviles.

6. Felicitaciones

¡Felicitaciones! Completaste correctamente este codelab y aprendiste a hacer apps adaptables con Jetpack Compose.

Aprendiste a verificar el tamaño y el estado de plegado de un dispositivo, y a actualizar la IU, la navegación y otras funciones de tu app adecuadamente. También aprendiste cómo la adaptabilidad mejora la accesibilidad y la experiencia del usuario.

Próximos pasos

Consulta los otros codelabs sobre la ruta de aprendizaje de Compose.

Apps de ejemplo

  • Los ejemplos de Compose son una colección de muchas apps que incorporan las prácticas recomendadas que se explican en los codelabs.

Documentos de referencia