Texto en Compose

Organiza tus páginas con colecciones Guarda y categoriza el contenido según tus preferencias.

El texto es una pieza central de cualquier IU, y Jetpack Compose facilita su visualización o escritura. Compose aprovecha la composición de sus componentes básicos, lo que significa que no es necesario anular propiedades ni métodos, ni extender grandes clases para tener una lógica y un diseño componibles específicos que funcionen como tú quieres.

En su nivel más básico, Compose brinda un BasicText y un BasicTextField, que son elementos básicos para mostrar texto y controlar entradas del usuario. En un nivel superior, Compose brinda Text y TextField, que son elementos que admiten composición y siguen los lineamientos de Material Design. Se recomienda su uso, ya que tienen el aspecto correcto para los usuarios de Android. También incluyen otras opciones que simplifican su personalización sin tener que escribir mucho código.

Cómo mostrar texto

La forma más básica de mostrar texto es usar el elemento componible Text con una String como argumento:

@Composable
fun SimpleText() {
    Text("Hello World")
}

La frase "Hello World" en texto negro sin formato

Cómo mostrar texto desde un recurso

Te recomendamos que uses recursos de string en lugar de codificar valores Text, ya que puedes compartir las mismas strings con Views de Android y preparar tu app para la internacionalización:

@Composable
fun StringResourceText() {
    Text(stringResource(R.string.hello_world))
}

Cómo aplicar estilo al texto

El elemento componible Text tiene varios parámetros opcionales para aplicarle estilo a su contenido. A continuación, incluimos algunos parámetros que abarcan los casos de uso más comunes con texto. Para ver todos los parámetros de Text, te recomendamos que revises el código fuente de texto de Compose.

Cada vez que configuras uno de estos parámetros, aplicas el estilo a todo el valor de texto. Si necesitas aplicar varios estilos en la misma línea o párrafos, consulta la sección sobre varios estilos intercalados.

Cómo cambiar el color del texto

@Composable
fun BlueText() {
    Text("Hello World", color = Color.Blue)
}

La frase "Hello World" en texto azul

Cómo cambiar el tamaño del texto

@Composable
fun BigText() {
  Text("Hello World", fontSize = 30.sp)
}

La frase "Hello World" en un tamaño de fuente más grande

Cómo convertir texto en cursiva

Usa el parámetro fontStyle para que el texto aparezca en cursiva (o configurar otro FontStyle).

@Composable
fun ItalicText() {
  Text("Hello World", fontStyle = FontStyle.Italic)
}

La frase "Hello World" en cursiva

Cómo aplicar negrita al texto

Usa el parámetro fontWeight para que el texto aparezca en negrita (o configura otro FontWeight).

@Composable
fun BoldText() {
    Text("Hello World", fontWeight = FontWeight.Bold)
}

La frase "Hello World" en negrita

Alineaciones de texto

El parámetro textAlign permite establecer la alineación del texto dentro de un área de superficie componible Text.

De forma predeterminada, Text seleccionará la alineación del texto natural según su valor de contenido:

  • Borde izquierdo del contenedor Text para alfabetos de izquierda a derecha, como el latino, el cirílico o el hangul
  • Borde derecho del contenedor Text para alfabetos de derecha a izquierda, como el árabe o el hebreo
@Preview(showBackground = true)
@Composable
fun CenterText() {
    Text("Hello World", textAlign = TextAlign.Center,
                modifier = Modifier.width(150.dp))
}

La frase "Hello World" centrada en su elemento contenedor

Si quieres configurar manualmente la alineación del texto de un elemento componible Text, usa TextAlign.Start y TextAlign.End en lugar de TextAlign.Left y TextAlign.Right, respectivamente, ya que se resuelven en el borde derecho de Text, según la orientación de texto del idioma preferido. Por ejemplo, TextAlign.End se alinea con el lado derecho para el texto en francés y con el lado izquierdo para el texto en árabe, pero TextAlign.Right se alinea con el lado derecho, sin importar qué alfabeto se use.

Sombra

El parámetro style permite establecer un objeto de tipo TextStyle y configurar varios parámetros, como la sombra. Shadow recibe un color para la sombra, el desplazamiento o su ubicación con respecto al Text y el radio de desenfoque, que es el aspecto borroso.

@Preview(showBackground = true)
@Composable
fun TextShadow() {
    val offset = Offset(5.0f, 10.0f)
    Text(
        text = "Hello world!",
        style = TextStyle(
            fontSize = 24.sp,
            shadow = Shadow(
                color = Color.Blue,
                offset = offset,
                blurRadius = 3f
            )
        )
    )
}

La frase "Hello World" con una sombra azul

Cómo trabajar con fuentes

Text tiene un parámetro fontFamily para permitir la configuración de la fuente usada en el elemento componible. Según la configuración predeterminada, se incluyen las familias de fuentes serif, Sans Serif, monoespaciales y cursivas:

@Composable
fun DifferentFonts() {
    Column {
        Text("Hello World", fontFamily = FontFamily.Serif)
        Text("Hello World", fontFamily = FontFamily.SansSerif)
    }
}

La frase "Hello World" en dos fuentes diferentes, con y sin serif

Puedes usar el atributo fontFamily para trabajar con fuentes personalizadas y tipos de letra definidos en la carpeta res/font:

Representación gráfica de la carpeta res > font en el entorno de desarrollo

En este ejemplo, se muestra el modo en que definirás una fontFamily según esos archivos de fuente con el uso de la función Font:

val firaSansFamily = FontFamily(
        Font(R.font.firasans_light, FontWeight.Light),
        Font(R.font.firasans_regular, FontWeight.Normal),
        Font(R.font.firasans_italic, FontWeight.Normal, FontStyle.Italic),
        Font(R.font.firasans_medium, FontWeight.Medium),
        Font(R.font.firasans_bold, FontWeight.Bold)
)

Por último, puedes pasar esta fontFamily a tu objeto Text componible. Debido a que una fontFamily puede incluir diferentes pesos, puedes configurar manualmente fontWeight a fin de seleccionar el peso correcto para tu texto:

Column {
    Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Light)
    Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Normal)
    Text(
        ..., fontFamily = firaSansFamily, fontWeight = FontWeight.Normal,
        fontStyle = FontStyle.Italic
    )
    Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Medium)
    Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Bold)
}

La frase "Hello World" en varios pesos y estilos diferentes

Para descubrir cómo establecer la tipografía en toda tu app, consulta la documentación sobre temas.

Varios estilos en un texto

Para configurar estilos diferentes en el mismo elemento que admite composición Text, debes usar una AnnotatedString, una string que se puede anotar con estilos de anotaciones arbitrarias.

AnnotatedString es una clase de datos que contiene lo siguiente:

  • Un valor Text
  • Una List de SpanStyleRange, equivalente al estilo intercalado con el rango de posición dentro del valor de texto
  • Una List de ParagraphStyleRange que especifica la alineación del texto, la dirección del texto, la altura de la línea y el estilo de sangría del texto

TextStyle es para uso en el elemento componible Text, mientras que SpanStyle y ParagraphStyle se usan en AnnotatedString.

La diferencia entre SpanStyle y ParagraphStyle es que ParagraphStyle se puede aplicar a un párrafo completo, mientras que SpanStyle puede aplicarse a nivel de carácter. Una vez que una parte del texto se marca con un ParagraphStyle, esa parte queda separada del resto como si tuviera feeds de líneas al principio y al final.

AnnotatedString tiene un compilador de tipo seguro para facilitar la creación: buildAnnotatedString.

@Composable
fun MultipleStylesInText() {
    Text(
        buildAnnotatedString {
            withStyle(style = SpanStyle(color = Color.Blue)) {
                append("H")
            }
            append("ello ")

            withStyle(style = SpanStyle(fontWeight = FontWeight.Bold, color = Color.Red)) {
                append("W")
            }
            append("orld")
        }
    )
}

La frase "Hello World" con varios cambios de estilo intercalados; la H es azul, y la W es roja y está en negrita

Podemos configurar ParagraphStyle de la misma manera:

@Composable
fun ParagraphStyle() {
    Text(
        buildAnnotatedString {
            withStyle(style = ParagraphStyle(lineHeight = 30.sp)) {
                withStyle(style = SpanStyle(color = Color.Blue)) {
                    append("Hello\n")
                }
                withStyle(
                    style = SpanStyle(
                        fontWeight = FontWeight.Bold,
                        color = Color.Red
                    )
                ) {
                    append("World\n")
                }
                append("Compose")
            }
        }
    )
}

Tres párrafos en tres estilos diferentes: azul, rojo y en negrita, y negro simple

Cantidad máxima de líneas

Para limitar la cantidad de líneas visibles en un elemento componible Text, establece el parámetro maxLines:

@Composable
fun LongText() {
    Text("hello ".repeat(50), maxLines = 2)
}

Un pasaje de texto largo truncado después de dos líneas

Desbordamiento de texto

A la hora de limitar un texto largo, es posible que desees indicar un TextOverflow, que solo se muestra si el texto está truncado. Para hacerlo, configura el parámetro textOverflow:

@Composable
fun OverflowedText() {
    Text("Hello Compose ".repeat(50), maxLines = 2, overflow = TextOverflow.Ellipsis)
}

Un largo pasaje de texto truncado después de tres líneas, con una elipsis al final

APIs de includeFontPadding y lineHeight

includeFontPadding es una propiedad heredada que agrega padding adicional en función de las métricas de fuente en la parte superior de la primera línea y en la parte inferior de la última línea de un texto. En Compose 1.2.0, includeFontPadding se configura como true de forma predeterminada.

Te recomendamos configurar includeFontPadding como false (que quitará el padding adicional) con la API experimental u obsoleta de PlatformTextStyle en Compose 1.2.0. y ajustar el texto si es necesario.

@Composable
fun AlignedText() {
    Text(
        text = myText,
        style = LocalTextStyle.current.merge(
            TextStyle(
                lineHeight = 2.5.em,
                platformStyle = PlatformTextStyle(
                    includeFontPadding = false
                ),
                lineHeightStyle = LineHeightStyle(
                    alignment = LineHeightStyle.Alignment.Center,
                    trim = LineHeightStyle.Trim.None
                )
            )
        )
    )
}

En las próximas versiones de Compose, includeFontPadding se configurará como false de forma predeterminada y se quitará la API de PlatformTextStyle.

Consulta la entrada de blog para corregir el padding de fuentes en Compose Text a fin de obtener más información sobre el contexto de este cambio, el funcionamiento de includeFontPadding en el sistema Vista y los cambios que realizamos en Compose y las nuevas APIs LineHeightStyle.

Temas

Para usar un tema en una app a fin de diseñar estilos de texto, consulta la documentación sobre temas.

Interacciones del usuario

Jetpack Compose habilita la interactividad detallada en Text. La selección de texto ahora es más flexible y se puede realizar en diseños componibles. Las interacciones de los usuarios en el texto son diferentes de otros diseños componibles, ya que no puedes agregar un modificador a una parte de un elemento Text componible. En esta sección, se destacan las diferentes API para permitir interacciones de usuario.

Cómo seleccionar texto

Según la configuración predeterminada, no se pueden seleccionar elementos componibles, lo que significa que, de forma predeterminada, los usuarios no pueden seleccionar y copiar texto desde tu app. Para habilitar la selección de texto, debes unir tus elementos de texto con un elemento componible SelectionContainer:

@Composable
fun SelectableText() {
    SelectionContainer {
        Text("This text is selectable")
    }
}

Un pasaje de texto breve seleccionado por el usuario

Puede que quieras inhabilitar la selección en partes específicas de un área seleccionable. Para hacerlo, debes unir la parte que no se puede seleccionar con un elemento componible DisableSelection:

@Composable
fun PartiallySelectableText() {
    SelectionContainer {
        Column {
            Text("This text is selectable")
            Text("This one too")
            Text("This one as well")
            DisableSelection {
                Text("But not this one")
                Text("Neither this one")
            }
            Text("But again, you can select this one")
            Text("And this one too")
        }
    }
}

Un pasaje de texto más largo. El usuario intentó seleccionar todo el fragmento, pero no se resaltaron las dos líneas porque se les aplicó DisableSelection.

Cómo obtener la posición de un clic en el texto

Para escuchar los clics en Text, puedes agregar el modificador clickable. Sin embargo, si quieres obtener la posición de un clic dentro de un Text que admite composición (suponiendo que tienes acciones distintas para diferentes partes del texto), debes usar un ClickableText:

@Composable
fun SimpleClickableText() {
    ClickableText(
        text = AnnotatedString("Click Me"),
        onClick = { offset ->
            Log.d("ClickableText", "$offset -th character is clicked.")
        }
    )
}

Cómo hacer clic con la anotación

Si un usuario hace clic en un elemento componible Text, tal vez quieras adjuntar información adicional en una parte del valor Text, como una URL adjunta a una palabra específica a fin de abrirla en un navegador. Para hacerlo, debes adjuntar una anotación, que toma una etiqueta (String), un elemento (String) y un rango de texto como parámetros. Desde una AnnotatedString, estas anotaciones se pueden filtrar con sus etiquetas o rangos de texto. Veamos un ejemplo:

@Composable
fun AnnotatedClickableText() {
    val annotatedText = buildAnnotatedString {
        append("Click ")

        // We attach this *URL* annotation to the following content
        // until `pop()` is called
        pushStringAnnotation(tag = "URL",
                             annotation = "https://developer.android.com")
        withStyle(style = SpanStyle(color = Color.Blue,
                                    fontWeight = FontWeight.Bold)) {
            append("here")
        }

        pop()
    }

    ClickableText(
        text = annotatedText,
        onClick = { offset ->
            // We check if there is an *URL* annotation attached to the text
            // at the clicked position
            annotatedText.getStringAnnotations(tag = "URL", start = offset,
                                                    end = offset)
                .firstOrNull()?.let { annotation ->
                    // If yes, we log its value
                    Log.d("Clicked URL", annotation.item)
                }
        }
    )
}

Cómo ingresar y modificar texto

TextField permite a los usuarios ingresar y modificar texto. Hay dos niveles de implementaciones TextField:

  1. TextField es la implementación de Material Design. Te recomendamos que elijas esta implementación, ya que sigue los lineamientos de Material Design:
    • El estilo predeterminado está relleno.
    • OutlinedTextField es la versión de estilo de contorno.
  2. BasicTextField permite a los usuarios editar texto con el teclado en pantalla o físico, pero no proporciona decoraciones como sugerencias o marcadores de posición.
@Composable
fun SimpleFilledTextFieldSample() {
    var text by remember { mutableStateOf("Hello") }

    TextField(
        value = text,
        onValueChange = { text = it },
        label = { Text("Label") }
    )
}

Un campo de texto editable que contiene la palabra "Hello". El campo tiene la etiqueta no editable "label".

@Composable
fun SimpleOutlinedTextFieldSample() {
    var text by remember { mutableStateOf("") }

    OutlinedTextField(
        value = text,
        onValueChange = { text = it },
        label = { Text("Label") }
    )
}

Campo de texto editable, con la etiqueta y un borde púrpura.

Cómo aplicar estilo a TextField

TextField y BasicTextField comparten muchos parámetros comunes de personalización. La lista completa para TextField está disponible en el código fuente de TextField. Esta es una lista no exhaustiva de algunos de los parámetros útiles:

  • singleLine
  • maxLines
  • textStyle
@Composable
fun StyledTextField() {
    var value by remember { mutableStateOf("Hello\nWorld\nInvisible") }

    TextField(
        value = value,
        onValueChange = { value = it },
        label = { Text("Enter text") },
        maxLines = 2,
        textStyle = TextStyle(color = Color.Blue, fontWeight = FontWeight.Bold),
        modifier = Modifier.padding(20.dp)
    )
}

Un TextField de varias líneas, con dos líneas editables más la etiqueta

Recomendamos usar TextField en lugar de BasicTextField cuando el diseño llame a un campo TextField o OutlineTextField de Material. Sin embargo, BasicTextField debe usarse cuando se crean diseños que no necesitan las decoraciones de la especificación de Material.

Opciones del teclado

TextField te permite establecer opciones de configuración del teclado, como el diseño, o bien habilitar la autocorrección si es compatible con el teclado. Es posible que algunas opciones no estén garantizadas si el teclado en pantalla no cumple con las opciones que se proporcionan aquí. Esta es la lista de las opciones de teclado compatibles:

  • capitalization
  • autoCorrect
  • keyboardType
  • imeAction

Formato

TextField te permite configurar un VisualTransformation para el valor de entrada, como reemplazar caracteres por * para contraseñas o insertar guiones cada 4 dígitos para un número de tarjeta de crédito:

@Composable
fun PasswordTextField() {
    var password by rememberSaveable { mutableStateOf("") }

    TextField(
        value = password,
        onValueChange = { password = it },
        label = { Text("Enter password") },
        visualTransformation = PasswordVisualTransformation(),
        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
    )
}

Un campo de entrada de texto con contraseña, con el texto enmascarado

Puedes encontrar más ejemplos en el código fuente de VisualTransformSamples.

Cómo limpiar la información de entrada

Una tarea común a la hora de editar texto es quitar caracteres iniciales o transformar la string de entrada cada vez que cambia.

Como modelo, debes suponer que el teclado puede realizar ediciones arbitrarias y grandes en cada instancia de onValueChange. Esto puede ocurrir, por ejemplo, si el usuario usa la autocorrección, reemplaza una palabra por un emoji o utiliza alguna otra función de edición inteligente. Para solucionar esto de forma correcta, escribe cualquier lógica de transformación suponiendo que el texto actual que se pasará a onValueChange no está relacionado con los valores anteriores o siguientes que se pasarán a onValueChange.

A fin de implementar un campo de texto que no permita ceros iniciales, puedes quitar todos los ceros iniciales en cada cambio de valor.

@Composable
fun NoLeadingZeroes() {
  var input by rememberSaveable { mutableStateOf("") }
  TextField(
      value = input,
      onValueChange = { newText ->
          input = newText.trimStart { it == '0' }
      }
  )
}

Para controlar la posición del cursor mientras limpias texto, usa la sobrecarga de TextFieldValue de TextField como parte del estado.

Fuentes descargables

A partir de Compose 1.2-alpha07, puedes usar la API de fuentes descargables en tu app de Compose para descargar fuentes de Google de forma asíncrona y usarlas en tu app.

Por el momento, no se admiten las fuentes descargables que proporcionan los proveedores personalizados.

Cómo usar las fuentes descargables de manera programática

Para descargar una fuente de manera programática desde tu app, sigue estos pasos:

  1. Agrega la dependencia:

    Groovy

    dependencies {
        ...
        implementation "androidx.compose.ui:ui-text-google-fonts:1.2.1"
    }
    

    Kotlin

    dependencies {
        ...
        implementation("androidx.compose.ui:ui-text-google-fonts:1.2.1")
    }
  2. Inicializa el objeto GoogleFont.Provider con las credenciales de Google Fonts.
    @OptIn(ExperimentalTextApi::class)
    val provider = GoogleFont.Provider(
       providerAuthority = "com.google.android.gms.fonts",
       providerPackage = "com.google.android.gms",
       certificates = R.array.com_google_android_gms_fonts_certs
    )
    
    Los parámetros que recibe el proveedor son los siguientes:
    • La autoridad del proveedor de fuentes de Google Fonts
    • El paquete del proveedor de fuentes para verificar su identidad
    • Una lista de conjuntos de hashes para los certificados a fin de verificar la identidad del proveedor (puedes encontrar los valores hash necesarios para el proveedor de Google Fonts en el archivo font_certs.xml, en la app de ejemplo de JetChat)
    Ten en cuenta que debes agregar la anotación ExperimentalTextApi para poder usar las API de fuentes descargables en tu app.
  3. Define un objeto FontFamily de la siguiente manera:
    import androidx.compose.ui.text.googlefonts.GoogleFont
    import androidx.compose.ui.text.font.FontFamily
    import androidx.compose.ui.text.googlefonts.Font
    
    val fontName = GoogleFont("Lobster Two")
    
    val fontFamily = FontFamily(
       Font(googleFont = fontName, fontProvider = provider)
    )
    
    Puedes consultar otros parámetros para tu fuente, como el grosor y el estilo, con FontWeight y FontStyle, respectivamente:
    import androidx.compose.ui.text.googlefonts.GoogleFont
    import androidx.compose.ui.text.font.FontFamily
    import androidx.compose.ui.text.googlefonts.Font
    
    val fontName = GoogleFont("Lobster Two")
    
    val fontFamily = FontFamily(
       Font(googleFont = fontName, fontProvider = provider,
            weight = FontWeight.Bold, style = FontStyle.Italic)
    )
    
  4. Configura el FontFamily que se usará en la función de componibilidad de texto, y eso es todo.
    Text(
        fontFamily = fontFamily,
        text = "Hello World!"
    )
    
    También puedes definir la tipografía para usar tu FontFamily.
    val MyTypography = Typography(
       body1 = TextStyle(
       fontFamily = fontFamily,
       fontWeight = FontWeight.Normal,
       fontSize = ...
    ),
       body2 = TextStyle(
       fontFamily = fontFamily,
       fontWeight = FontWeight.Bold,
       letterSpacing = ...
    ),
       h4 = TextStyle(
       fontFamily = fontFamily,
       fontWeight = FontWeight.SemiBold
       ...
    ),
    ...
    
    Luego, establece la tipografía en el tema de tu app:
    MyAppTheme(
       typography = MyTypography
    ) {
    ...
    

Para ver un ejemplo de una app que implementa fuentes descargables en Compose junto con Material3, asegúrate de revisar la app de ejemplo de JetChat.

Fuentes de resguardo

Puedes determinar una cadena de resguardo para tu fuente, en caso de que esta no se descargue correctamente. Por ejemplo, si tienes una fuente descargable definida de la siguiente manera:

import androidx.compose.ui.text.googlefonts.Font

val fontName = GoogleFont("Lobster Two")

val fontFamily = FontFamily(
   Font(googleFont = fontName, fontProvider = provider),
   Font(googleFont = fontName, fontProvider = provider, weight = FontWeight.Bold)
)

Puedes definir los valores predeterminados de la fuente para ambos grosores, de la siguiente manera:

import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.googlefonts.Font

val fontName = GoogleFont("Lobster Two")

val fontFamily = FontFamily(
   Font(googleFont = fontName, fontProvider = provider),
   Font(resId = R.font.my_font_regular),
   Font(googleFont = fontName, fontProvider = provider, weight = FontWeight.Bold),
   Font(resId = R.font.my_font_regular_bold, weight = FontWeight.Bold)
)

Asegúrate de agregar las importaciones correctas.

Cuando defines FontFamily de esta manera, se crea una FontFamily que contiene dos cadenas, una por cada grosor. El mecanismo de carga intentará resolver la fuente en línea primero y, luego, la fuente ubicada en la carpeta de recursos R.font locales.

Cómo depurar tu implementación

Para ayudarte a verificar si la fuente se descarga correctamente, puedes definir un controlador de corrutinas de depuración. Tu controlador proporciona el comportamiento de lo que se debe hacer en caso de que la fuente no se cargue de forma asíncrona.

Para comenzar, crea un CoroutineExceptionHandler.

val handler = CoroutineExceptionHandler { _, throwable ->
   // process the Throwable
   Log.e(TAG, "There has been an issue: ", throwable)
}

Luego, pásalo al método createFontFamilyResolver para que el agente de resolución use el nuevo controlador:

CompositionLocalProvider(
        LocalFontFamilyResolver provides createFontFamilyResolver(LocalContext.current, handler)
    ) {
        Column {
            Text(
                text = "Hello World!",
                style = MaterialTheme.typography.body1
            )
        }
    }

También puedes usar la API de isAvailableOnDevice del proveedor para probar si está disponible y si los certificados están configurados de forma correcta. Para ello, puedes llamar al método isAvailableOnDevice, que muestra el valor "false" si el proveedor no está bien configurado.

val context = LocalContext.current
LaunchedEffect(Unit) {
   if (provider.isAvailableOnDevice(context)) {
       Log.d(TAG, "Success!")
   }
}

Advertencias

Google Fonts tarda varios meses en habilitar las nuevas fuentes en Android. Existe un intervalo de tiempo entre el momento en que se agrega una fuente en fonts.google.com y el momento en que está disponible desde la API de fuentes descargables (ya sea en el sistema de vistas o en Compose). Es posible que las fuentes agregadas recientemente no se carguen en tu app con una IllegalStateException. Para ayudar a los desarrolladores a identificar este error entre otros tipos de errores de carga de fuentes, agregamos mensajes descriptivos de la excepción en Compose con los cambios aquí.