Cómo integrar Compose con tu IU existente

Si tienes una app con una IU basada en objetos View, es posible que no quieras volver a escribir toda su IU de una sola vez. Esta página te ayudará a agregar elementos de Compose nuevos a tu IU existente.

Cómo migrar la IU compartida

Si migras gradualmente a Compose, es posible que necesites usar elementos compartidos de la IU en el sistema de Compose y de View. Por ejemplo, si tu app tiene un componente CallToActionButton personalizado, es posible que debas usarlo en pantallas basadas en Compose y en View.

En Compose, los elementos compartidos de la IU se convierten en elementos que admiten composición y que se pueden volver a usar en la app, independientemente de que el elemento al que se le aplica el estilo utilice XML o sea una vista personalizada. Por ejemplo, deberías crear un elemento CallToActionButton que admita composición para tu componente Button de llamada a la acción personalizada.

Para usar el elemento que admite composición en las pantallas basadas en View, debes crear un wrapper de vista personalizado que se extienda desde AbstractComposeView. En su elemento Content anulado que admite composición, coloca el elemento que creaste unido al tema de Compose como se muestra en el siguiente ejemplo:

@Composable
fun CallToActionButton(
    text: String,
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
) {
    Button(
        colors = ButtonDefaults.buttonColors(
            backgroundColor = MaterialTheme.colors.secondary
        ),
        onClick = onClick,
        modifier = modifier,
    ) {
        Text(text)
    }
}

class CallToActionViewButton @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = 0
) : AbstractComposeView(context, attrs, defStyle) {

    var text by mutableStateOf<String>("")
    var onClick by mutableStateOf<() -> Unit>({})

    @Composable
    override fun Content() {
        YourAppTheme {
            CallToActionButton(text, onClick)
        }
    }
}

Ten en cuenta que los parámetros que admiten composición se convierten en variables mutables dentro de la vista personalizada. Eso hace que la vista CallToActionViewButton personalizada aumente y se pueda usar (por ejemplo, con la vinculación de vistas), como una vista tradicional. Consulta el siguiente ejemplo:

class ExampleActivity : Activity() {

    private lateinit var binding: ActivityExampleBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityExampleBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.callToAction.apply {
            text = getString(R.string.something)
            onClick = { /* Do something */ }
        }
    }
}

Si el componente personalizado contiene un estado mutable, consulta la fuente de información de estado.

Temas

Según Material Design, el uso de la biblioteca Componentes de Material Design para Android (MDC) es la manera recomendada de diseñar apps para Android. Como se explica en la documentación de temas de Compose, Compose implementa esos conceptos con el elemento que admite composición MaterialTheme.

Cuando crees pantallas nuevas en Compose, asegúrate de aplicar un MaterialTheme antes de cualquier elemento que admita composición que emita IU desde la biblioteca de componentes materiales. Los componentes materiales (Button, Text, etc.) dependen de que se implemente un MaterialTheme, y su comportamiento no está definido sin él.

Todos los ejemplos de Jetpack Compose usan un tema de Compose personalizado creado sobre la base de MaterialTheme.

Varias fuentes de confianza

Es probable que una app existente tenga una gran cantidad de temas y estilos para las vistas. Si implementas Compose en una app existente, necesitarás migrar el tema a fin de usar MaterialTheme para cualquier pantalla de Compose. Eso significa que el tema de tu app tendrá 2 fuentes de confianza: un tema basado en la vista y otro basado en Compose. Si decides realizar cambios en tu estilo, deberás hacerlos en varios lugares.

Si tu plan es migrar por completo la app a Compose, eventualmente deberás crear una versión de Compose del tema existente. El problema es que, cuanto antes en el proceso de desarrollo crees tu tema de Compose, más mantenimiento tendrás que hacer durante el proceso.

Adaptador de temas de MDC Compose

Si utilizas la biblioteca de MDC en tu app para Android, la biblioteca del Adaptador de temas de MDC Compose te permitirá volver a usar fácilmente el color, la tipografía y la forma de tus temas basados en View existentes en tus elementos que admiten composición.

import com.google.android.material.composethemeadapter.MdcTheme

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

        setContent {
            MdcTheme {
                // Colors, typography, and shape have been read from the
                // View-based theme used in this Activity
                ExampleComposable(/*...*/)
            }
        }
    }
}

Consulta la documentación sobre la biblioteca de MDC para obtener más información.

Adaptador de temas de AppCompat Compose

La biblioteca del Adaptador de temas de AppCompat Compose te permite volver a usar fácilmente temas de XML de AppCompat para la creación de temas en Jetpack Compose. Crea un MaterialTheme con los valores de color y tipografía del tema del contexto.

import com.google.accompanist.appcompattheme.AppCompatTheme

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

        setContent {
            AppCompatTheme {
                // Colors, typography, and shape have been read from the
                // View-based theme used in this Activity
                ExampleComposable(/*...*/)
            }
        }
    }
}

Estilos de componentes predeterminados

Las bibliotecas de MDC y del Adaptador de temas de AppCompat Compose no leen ningún estilo de widget predeterminado definido por temas. Eso se debe a que Compose no tiene el concepto de elementos que admiten composición predeterminados.

Obtén más información sobre los estilos de componentes y los sistemas de diseño personalizados en la documentación de temas.

Superposiciones de temas en Compose

Cuando migres pantallas basadas en View a Compose, presta atención a los usos del atributo android:theme. Es probable que necesites un nuevo MaterialTheme en esa parte del árbol de IU de Compose.

Obtén más información al respecto en la guía sobre temas.

Animaciones de WindowInsets e IME

Puedes controlar WindowInsets si usas la biblioteca de inserciones de acompañamiento, que brinda elementos que admiten composición y modificadores para controlar las inserciones en tus diseños, además de compatibilidad con las animaciones IME.

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

        WindowCompat.setDecorFitsSystemWindows(window, false)

        setContent {
            MaterialTheme {
                ProvideWindowInsets {
                    MyScreen()
                }
            }
        }
    }
}

@Composable
fun MyScreen() {
    Box {
        FloatingActionButton(
            modifier = Modifier
                .align(Alignment.BottomEnd)
                .padding(16.dp) // normal 16dp of padding for FABs
                .navigationBarsPadding(), // Move it out from under the nav bar
            onClick = { }
        ) {
            Icon( /* ... */)
        }
    }
}

Animación que muestra un elemento de IU que se desplaza desde arriba hacia abajo a fin de dejar lugar para un teclado.

Figura 2: Animaciones IME que usan la biblioteca de inserciones de acompañamiento

Si deseas obtener más información, consulta la documentación de la biblioteca de inserciones de acompañamiento.

Cómo controlar cambios de tamaños de pantalla

Si migras una app que usa distintos diseños de XML según el tamaño de la pantalla, utiliza el elemento que admite composición BoxWithConstraints para conocer el tamaño mínimo y máximo que puede ocupar un elemento que admite composición.

@Composable
fun MyComposable() {
    BoxWithConstraints {
        if (minWidth < 480.dp) {
            /* Show grid with 4 columns */
        } else if (minWidth < 720.dp) {
            /* Show grid with 8 columns */
        } else {
            /* Show grid with 12 columns */
        }
    }
}