Ingeniero de software
Este año, Android adoptó oficialmente el enfoque Compose-first para compilar todas las IU, lo que colocó las vistas heredadas en modo de mantenimiento. Para Google Play, ese recorrido comenzó en 2020. En una huella de ingeniería que abarca casi dos décadas y miles de millones de usuarios, la migración a Jetpack Compose se logró a través de prácticas recomendadas comprobadas, una estrecha colaboración con el equipo principal de Compose y una evolución continua de las herramientas.
Descubre cómo la modernización de Google Play con Compose generó mejoras medibles en la velocidad y la estabilidad a largo plazo. En la sesión, se explica cómo conectar los flujos de trabajo de diseño de Figma basados en agentes con la implementación de Compose, automatizar la calidad para evitar recomposiciones innecesarias y crear interfaces expresivas con colores dinámicos, tipografía enriquecida y formas en capas. Te irás con estrategias probadas para clasificar errores más rápido y escalar tu propia migración de Compose con confianza.
ComposeView directamente en la raíz de la actividad.MaterialTheme y la biblioteca de Material Color Utilities. Esto evita que se destruyan las actividades, aunque generar esquemas de colores durante el tiempo de compilación ayuda a limitar la latencia del tiempo de ejecución.BoxWithConstraints.Transcripción
¡Hola a todos!
Me llamo Jon.
Me uno a ustedes desde el equipo de Google Play Store.
Este es mi colega Kirill.
Hoy hablaremos sobre cómo trasladamos mucho código del sistema de View y Fragment a Compose.
Empecemos.
¿Cómo fue esa adopción y cuál fue su escala?
En el caso de la base de código de nuestra app, creció de forma orgánica desde 2007.
Tenemos más de un millón de líneas de código de producción, 200 pantallas y 500 componentes de IU únicos que se usan en muchas de esas pantallas.
En 2020, decidimos que la velocidad, la confiabilidad y el rendimiento de los desarrolladores no estaban alcanzando los objetivos que queríamos.
Por lo tanto, decidimos adoptar una pila tecnológica de IU moderna.
Evaluamos un montón de bibliotecas diferentes.
También teníamos algunos amigos en AndroidX que nos hablaban sobre Compose en ese momento, y decidimos adoptar Compose y convertirnos en el primer socio importante de Compose.
¿Cómo fue?
Seis años después, el 97% de las impresiones de los usuarios en la app de Play Store se producen en pantallas que solo usan Compose.
Todo el desarrollo para teléfonos y pantallas grandes se trasladó a Compose-only desde 2020.
Incluso nuestros factores de forma extendidos, RE, TV, Auto y Wear, se trasladaron a Compose desde 2025.
Compose ha sido un gran impulsor de ese éxito y el motivo por el que seguimos adoptándolo cada vez más.
Tienen un paquete de pruebas muy sólido y completo que nos ayudó a adoptar nuevas versiones de Compose dos veces por semana.
Incorporar sus compilaciones alfa y sus nuevas APIs nos ayuda a aprovechar las nuevas correcciones de errores y mejoras de rendimiento, y a explorar algunas de esas nuevas APIs y brindar nuevas experiencias del usuario.
En particular, en lo que respecta al rendimiento, Compose realmente mejoró el rendimiento de nuestra app en todas las pantallas principales.
Esta es la página de resultados de búsqueda, la página principal y la página de detalles.
Estas son métricas muy importantes (rendimiento, jank y latencia de carga) para nuestro negocio y también para la experiencia del usuario.
Por lo tanto, hemos visto una mejora del 30% en los últimos 14 meses.
Pero ¿cómo llegamos hasta aquí?
De eso venimos a hablar.
Hablemos del plan de migración.
Por lo tanto, Play adoptó un enfoque de varios pasos.
No queríamos arriesgarnos y poner todos los huevos en una sola canasta, por así decirlo.
Terminamos tomando algunas medidas para comprender los riesgos, cómo afecta a nuestro negocio y realmente generar confianza antes de apostar todo.
Por lo tanto, los nombraré: gatear, caminar, trotar y correr.
Por lo tanto, la fase de gateo para nosotros consistió en familiarizarnos con Compose.
Comenzamos con una página de poco tráfico.
Podría ser la página de configuración, las licencias de terceros, ese tipo de cosas.
Tu objetivo principal es familiarizarte con la biblioteca de Compose.
Por lo tanto, esto puede ser acostumbrarse a las filas, columnas, texto, imágenes, todo este tipo de cosas.
Luego, debes comenzar a incorporar toda la infraestructura de tu app y comprender cómo funciona en Compose.
Esto podría ser el registro de impresiones, el registro de clics, la tematización, estábamos renovando la carga de datos al mismo tiempo.
Así que un poco de todo esto.
Y algo que nos resultó útil para garantizar que todas nuestras pantallas se vean coherentes fue crear algunas pruebas de unidades y pruebas funcionales para verificar que nuestros objetos de temas en Compose se asignaran directamente a los estilos, la tipografía, los colores y todo lo demás en XML.
Una vez que nos familiarizamos con el proceso y nos sentimos cómodos, decidimos pasar a la caminata.
Para nosotros, esto significó tomar un componente más pequeño en una pantalla con mucho tráfico y evaluar cómo se vería ese tipo de migración.
Te recomendamos que tomes un elemento que esté en un RecyclerView para que puedas comprender cualquier problema de compatibilidad entre el sistema de View y el de Compose, y también ver el rendimiento de las mediciones y los tirones en el proceso.
El componente en sí no tiene que ser muy complejo, pero es importante que tenga algo que ejercite los diseños, como texto, imágenes y ese tipo de cosas, un par de instancias diferentes de lo que verías normalmente.
Un aspecto que debes tener en cuenta y que probablemente debas esperar en esta área es un aumento en los tirones y la latencia, que será leve, pero se debe a que ahora estás ejecutando dos pilas.
Tienes una pila de renderización que está toda en Compose y otra que está en Views.
Por lo tanto, habrá un poco de sobrecarga de memoria, carga de clases adicionales y todo eso.
Sin embargo, a medida que adoptes más Compose, eso desaparecerá.
Y si realmente lo necesitas mientras tanto, puedes realizar un ligero calentamiento previo de esa pantalla para ayudar a alinearla y mitigar ese costo a corto plazo.
Por lo tanto, después de caminar, pasamos a trotar.
Esto es para cuando ya nos sintamos muy cómodos.
En lugar de hacer un solo componente en una página de mucho tráfico, haz toda la página de mucho tráfico.
Por lo tanto, lo que hicimos fue mantener la biblioteca de navegación y usar fragmentos, pero cada pantalla individual se implementó por completo en Compose.
Hicimos que el fragmento devolviera la ComposeView en sí.
Por lo tanto, puedes esperar una mejora en la latencia y los tirones.
No tendrás tanta sobrecarga de memoria ni carga de clases como las que mencioné.
Compose también realiza algunos niveles de almacenamiento en caché a nivel de ComposeView.
Por lo tanto, si tienes un RecyclerView con un montón de tarjetas que se ven coherentes y podrían tener los mismos íconos, todo ese análisis de íconos y uso de recursos se comparte entre todo lo que hay en ComposeView.
Después de eso, pasamos a correr.
Aquí es cuando realmente te comprometes por completo.
Una vez que la mayor parte del tráfico de tu app provenga de Compose, subiremos un nivel.
Por encima de las pantallas, tenemos la actividad.
Por lo tanto, mueve ComposeView a la raíz de la actividad.
Y gran parte de tu código se verá relativamente similar.
Aún estás intentando dar una instrucción de navegación en la que quieres decir, por ejemplo, que se mueva de una pantalla a otra.
Pero, en lugar de realizar transacciones de fragmentos, llamas a una función de componibilidad.
Y puedes hacerlo sobre la biblioteca de Nav2 o la de Nav3, la que elijas.
En nuestro caso, no teníamos todas las pantallas migradas.
Mencioné que tenemos muchos.
Por lo tanto, creamos la compatibilidad con fragmentos, en la que puedes tener una pantalla basada en fragmentos y aprovechar los wrappers de AndroidFragment de Compose para hacerlo.
Lo que puedes esperar, de nuevo, es que la latencia y los tirones son importantes para nuestro negocio, por lo que seguimos superando los límites y tú sigues viendo mejoras, ya que ahora tenemos almacenamiento en caché en todas las pantallas y muchos menos problemas de paginación excesiva de memoria.
Pero también obtienes nuevas capacidades.
Tenemos un ejemplo de esto a la derecha en este GIF, en el que puedes realizar transiciones de elementos compartidos entre pantallas.
Por lo tanto, tenemos una imagen que se amplía a medida que pasas de una pantalla a la otra y que proporciona esa continuidad.
También puedes aprovechar toda la flexibilidad de animación de Compose mientras realizas esa transición completa.
Y, con eso, le daré la palabra a Kirill para que hable sobre algunas funciones más.
Gracias, Jon.
Quiero hablar un poco sobre un tema que me apasiona, que es la aplicación de temas y estilos para apps para Android, algo que vengo haciendo desde hace un tiempo.
En el mundo de View, había código Java y, un poco más tarde, código Kotlin, y luego había recursos XML, recursos como colores, cadenas, dimensiones, diseños, temas y estilos.
Y la forma en que funcionaba era que todos estos recursos XML se compilaban en una tabla de recursos que se incluía en el APK y era bastante estática.
Había una API Resources.Theme.applyStyle que podías usar de forma dinámica en el entorno de ejecución para aplicar un estilo personalizado sobre la jerarquía de la IU.
La principal limitación era o es que debes hacerlo antes de aumentar el contenido, lo que significa que, si quieres aplicar ese diseño de forma dinámica en un elemento existente de la IU, debes desglosar y volver a crear toda la actividad, lo que no era ideal para la experiencia del usuario.
Además, como se aplicaban estos bloques XML precompilados, se volvía un poco difícil combinar varias superposiciones dinámicas condicionales una encima de la otra en función de los parámetros de experimentación o cualquier otra necesidad empresarial.
Era un poco difícil crear un modelo mental adecuado de la jerarquía de estilos en ese mundo de XML.
Por último, no había una sola vista unificada en Android Studio ni en ninguna otra herramienta para desarrolladores que permitiera cerrar la brecha entre el mundo de Java, el mundo del código y el mundo de XML.
Y era un poco difícil hacer malabares y crear el modelo mental adecuado, y depurar por qué no se aplicaba mi estilo, por qué obtenía el color incorrecto, etcétera.
En el nuevo mundo, en el mundo de Compose, específicamente para el sistema de Material Design y la biblioteca de Compose Material, pero también para otros sistemas de diseño, lo que tenemos para el sistema de Material Design es una función de componibilidad MaterialTheme que aplicas en la parte superior de la jerarquía de la IU en la pantalla, y también puedes aplicar esta función de componibilidad MaterialTheme como una superposición en cualquier parte más pequeña de la IU.
Si miras la pantalla de la derecha, puedes ver este nivel Oro de Play Points en la parte superior, que es una pequeña tarjeta con el indicador de progreso y un par de textos.
Aplicamos esta superposición de Material Theme con un esquema de colores personalizado que genera la biblioteca de Material Color Utilities, disponible en GitHub para que la uses.
Pasamos este color marrón, amarillo, dorado como semilla, y genera el espectro completo de tokens de color de Material, primario sobre primario, secundario sobre secundario, que luego usamos dentro de la implementación de esta tarjeta.
Esto pone la capacidad completa de los temas dinámicos en manos de los desarrolladores de Android.
Todo se hace en código, todo se hace en Kotlin, lo que significa que es mucho más fácil crear el modelo mental adecuado de cómo funciona todo y es mucho más fácil de depurar.
De forma interna, estas superposiciones se adjuntan como CompositionLocals a los nodos de IU específicos de tu jerarquía, donde eliges aplicarlas.
Consideraciones de rendimiento: Generar un esquema de colores completo con la biblioteca de Material Color Utilities no es gratis.
Sin embargo, tiene una cierta sobrecarga.
Por lo tanto, si tienes algo lo suficientemente complicado que necesita varios de estos, algún tipo de caja bento o lo que sea, según las necesidades de tu producto, y tienes varios esquemas de colores que generas en el entorno de ejecución, eso comienza a acumularse, lo que aumenta la latencia y los tirones.
Por lo tanto, a) debes comenzar a medir cómo afecta tu rendimiento y b) puedes descargar parte de esta sobrecarga del tiempo de ejecución al tiempo de compilación.
Puedes usar la misma biblioteca de Material Color Utilities en el momento de la compilación para generar previamente esos esquemas de colores según tus necesidades de desarrollo de la marca o del producto, y descargar esta sobrecarga en el momento de la compilación.
En el caso de los CompositionLocals que mencioné que hacen un seguimiento de dónde se encuentran exactamente las superposiciones en el árbol de jerarquía de la IU, son mucho más económicos, pero no son gratuitos.
Por lo tanto, según la cantidad que uses, decenas o cientos, se empieza a acumular.
Y, en algún momento, querrás empezar a analizar el costo de todos estos elementos locales de composición combinados.
Este es un ejemplo de lo que mencionaba sobre las capacidades de Compose para aplicar temas dinámicos en el entorno de ejecución.
Aquí tenemos cuatro cuadrantes, las mismas imágenes, la misma, llamémosla, tarjeta de evento.
Tenemos esta imagen hero que abarca toda la tarjeta, una etiqueta en la parte superior izquierda, un botón en la parte inferior derecha y un par de textos.
Y en el lado derecho de cada uno de los cuadrantes, se ven los tokens de color que se usan como especificación de diseño para cada uno de los elementos.
La implementación de la tarjeta es exactamente la misma para estos cuatro casos.
La única diferencia es que la superposición de Temas de Material que une la tarjeta se genera o configura con un esquema de colores creado de forma dinámica según el color dominante de la imagen: azul, púrpura, amarillo y rojo.
Todo lo demás permanece igual.
No es necesario que destruyas y vuelvas a crear tu actividad.
Puedes aplicarlo de forma condicional según los parámetros de configuración de tu experimento o cualquier otra necesidad empresarial.
Y es un mundo mucho más dinámico, como ya dije, es un mundo mucho más dinámico, verdaderamente dinámico, de aplicar personalizaciones de color a tu app.
Ahora, Jon continuará con la siguiente sección.
Sí.
Esa es parte de la flexibilidad de la que hablaba Kirill.
Hablemos de confiabilidad y velocidad.
Por lo tanto, todos los desarrolladores y las empresas buscan crear código de alta calidad con rapidez.
Veamos cómo Compose ayudó a Play a lograrlo.
En primer lugar, sé que es un diagrama grande, pero la simplicidad de la arquitectura tiene enormes beneficios para todos los autores y encargados del mantenimiento del código, tanto humanos como agentes.
No voy a profundizar demasiado en esto, pero uno de los modelos mentales que Compose intenta implementar es el flujo de datos unidireccional, y realmente tuvo un gran impacto en nuestro equipo.
Para quienes no estén familiarizados con su funcionamiento, lo básico es que tienes una fuente de datos.
Una fuente de datos proporciona datos.
Esos datos se pueden transformar en una IU específica, que mostramos como un modelo de IU.
Luego, Compose puede observar eso con facilidad con solo llamar a una propiedad de valor en ese estado.
Esto se representa con las flechas que se encuentran en la parte superior de este diagrama.
Y, luego, los eventos fluyen en sentido contrario.
Por lo tanto, si un usuario hace clic en un botón, esto volverá al transformador de la IU, actualizará la fuente de datos, emitirá un valor nuevo, ejecutará las transformaciones y obtendrás este bucle.
Por lo tanto, todo se ejecuta a través de este bucle relativamente simple.
Los beneficios que realmente hemos visto de esto es que ayuda a reducir el período de adaptación.
No hay más preguntas como dónde se realizan todas estas llamadas al subproceso principal o fuera de estos otros subprocesos.
Sigue un bucle general.
Compose hace que la observación de datos sea muy sencilla, y los datos tienen garantías de coherencia sólida en las capas de la arquitectura.
El modelo mental redujo muchas conversaciones sobre las fuentes de confianza dentro de nuestro equipo y eliminó por completo varios tipos de condiciones de carrera.
Otro gran beneficio para nosotros, en comparación con nuestra arquitectura anterior de modelo-vista-controlador, es que se volvió muy, muy sencillo tener una ejecución paralela de fuentes de datos y transformaciones de IU.
Esto mejoró mucho nuestra latencia de carga en Play Store, en, ya sabes, decenas de 50 ms o algo así.
Fue bastante impresionante para nosotros.
Junto con el flujo unidireccional de datos, el otro gran defensor de Compose es la IU declarativa.
Kirill habló un poco de esto.
Por lo tanto, los desarrolladores de Play también disfrutaron mucho de esta función.
El flujo de control de la IU ahora está en un solo archivo.
A la derecha, tenemos un ejemplo bastante sencillo.
@Composable
fun PurchaseRow(onClick: () -> Unit, modifier: Modifier) {
Row(modifier) {
Text("20% off sale")
if (LocalExpts.current.useButtonV2) {
ButtonV2("Buy", onClick = onClick)
} else {
ButtonV1("Buy", onClick = onClick)
}
}
}Estamos cambiando entre dos implementaciones de botones según un parámetro de configuración experimental.
En lugar de tener que leer varios archivos de vista o, ya sabes, archivos de fragmentos y archivos en formato XML, solo lo lees de arriba a abajo, y eso lo hace mucho más simple.
El costo de anidar elementos componibles también es mucho más bajo que el de las jerarquías de vistas anidadas, y nos permitió organizar nuestro código de forma natural para que sea más legible, en lugar de tener que exprimir cada pequeño detalle para mejorar el rendimiento.
Otros beneficios que vimos son que las devoluciones de llamada del ciclo de vida son mucho más simples y se redujo la cantidad de devoluciones de llamada de las que debes preocuparte.
Por lo tanto, en lugar de tener un ciclo de vida de la vista, un ciclo de vida del fragmento y un ciclo de vida de la actividad, obtienes un efecto desechable, un efecto secundario y, según dónde lo coloques, actúa de una manera muy coherente.
Por lo tanto, eso ha sido un gran beneficio para nosotros.
En mi opinión, estos dos cambios de arquitectura realmente prepararon el terreno para los LLM y les permitieron generar código de muy alta calidad.
Y estoy seguro de que muchos de ustedes probablemente hayan probado esto o lo hayan hecho con su LLM favorito.
Puedes tomar una captura de pantalla con anotaciones de colores y tipografía, ingresarla y ver que puede generar filas, columnas, texto y botones, y hace un trabajo razonable.
Sin embargo, tiene sus limitaciones.
Y lo que Play realmente vio es que no comprende tu base de código, ¿verdad?
No sabe qué componentes ya creaste que debería reutilizar ni conoce los nombres de los tokens de diseño.
Solo te proporciona colores y cadenas hard-coded.
¿Qué contenido debería provenir del dispositivo, qué contenido debería provenir de un servidor o cómo me aseguro de que tenga un buen rendimiento usando LazyList y no solo creando una columna enorme?
Por lo tanto, para abordar algunos de estos problemas, anotamos esas capturas de pantalla con un poco más de metadatos.
Por lo tanto, nombres de IU, cuadros delimitadores para saber dónde comienzan y terminan los componentes, tokens de diseño para los nombres semánticos de los colores y la tipografía, habilidades y también algunas instrucciones para indicar dónde se colocará esto, de modo que pueda tener en cuenta esas consideraciones de rendimiento.
Y, luego, también una firma de función de nivel muy alto, para que puedas indicarle qué contenido es dinámico y cuál es estático.
Por lo tanto, terminas con algo como lo que teníamos antes, que se mueve al después.
No tengo los cuadros delimitadores aquí porque agregarían mucho ruido al diagrama.
Sin embargo, puedes ver que esos colores y nombres de tipografía hard-coded se reemplazan por title large y on surface.
Tenemos los nombres de los componentes, como el encabezado de la tarjeta y la insignia del ícono, claramente marcados.
De esa manera, se pueden encontrar en tu base de código.
Y, para nosotros, muchas de estas provienen de bibliotecas de diseño comunes, de las que Kirill hablará en un minuto.
Por lo tanto, no etiquetarás de forma personalizada todos estos productos todo el tiempo.
Si lo combinas con una firma de método como la que tenemos en la parte inferior, OfferCard, sabrá que el título, el endDate y la imagen son dinámicos.
No deberíamos hard-coded esos.
Y, junto con esos nombres semánticos, ahora podemos intercambiar todas esas aplicaciones de color diferentes de las que hablaba Kirill.
Y con eso, generamos mucho más código.
Le cederé la palabra a Kirill para que hable sobre cómo mantenemos la calidad.
Así es.
Gracias, Jon.
Para continuar con este tema de los LLM y el desarrollo asistido por agentes, primero comenzaremos con el diseño de UX.
Lo que debes hacer es, independientemente de la herramienta de diseño que elijas, tal vez Figma o alguna otra, tener una plantilla compartida que usen todos los diseñadores y que tenga tokens de color, de tipografía y de forma del sistema de diseño que elijas.
Tal vez sea Material.
Tal vez sea tu sistema de diseño personalizado.
Y, específicamente para los tokens de color, quieres que esas plantillas tengan compatibilidad integrada para cambiar entre el modo claro y el oscuro, y para cambiar entre los modos de contraste normal, medio y alto que se introdujeron en Android hace unos años.
Para que los diseñadores puedan cambiar rápidamente entre estas diferentes combinaciones y ver cómo responden sus especificaciones de diseño.
Luego, cuando se finaliza y se entrega al lado de la implementación, en esa canalización de exportación, quieres tener algún tipo de capacidad para hacer un seguimiento de que cada color, cada elemento de tipografía, cada forma use un token coincidente en lugar de ser un valor RGB codificado, o una familia de fuentes codificada, o un tamaño de fuente codificado.
Por último, lo que nos resultó muy útil a la escala de Google Play es que tenemos, no decenas, sino cientos de componentes, clústeres, tarjetas y contenedores que se usan en una variedad de pantallas.
Lo que nos resultó útil es hacer un seguimiento de todo esto en un solo lugar, lo que llamamos un catálogo de componentes, y luego tener un agente personalizado entrenado en ese catálogo de componentes.
Y resulta útil cuando comienzas una pantalla nueva, tal vez un nuevo recorrido del usuario, tal vez una nueva área de producto, y a partir de esas exploraciones de diseño iniciales, tal vez diapositivas, tal vez especificaciones de Figma, puedes alimentar a ese agente, y el agente ayuda a identificar rápidamente qué componentes ya existen para que no sea necesario rediseñarlos ni volver a especificarlos, de modo que no sea necesario volver a implementarlos desde cero.
Y eso reduce de manera bastante significativa los costos de diseño y de implementación.
En cuanto a la implementación, nos apoyamos mucho en las reglas de Lint principales y personalizadas.
Un par de reglas de Lint principales que quiero destacar son FrequentlyChangingValueDetector, que ayuda a identificar fuentes de recomposiciones innecesarias, que pueden generar latencia y bloqueos, y la otra es ScaffoldPaddingDetector para marcar casos en los que usamos el elemento componible Scaffold de Material Compose, pero ignoramos los paddings entrantes.
No es una fuente de latencia ni de tirones, pero, en algunos casos, puede generar desalineaciones de diseño muy difíciles de depurar, como cuando se trata de un diseño de borde a borde o de plegado y desplegado.
Por último, BoxWithConstraints incluye ciertas consideraciones de rendimiento.
Es posible que se necesiten dos marcos para diseñar todo correctamente.
No lo bloqueamos por completo, pero lo marcamos como una advertencia de Lint para que el desarrollador que trabaja en esa pantalla en particular sepa que está tomando una decisión específica de usar BoxWithConstraints o que tal vez pueda elegir usar otra cosa para ese diseño específico.
Para la automatización de temas, de nuevo, tokens de color, tokens de tipografía y tokens de forma.
Tenemos una regla de Lint que bloquea el uso de colores y tipografías codificados.
Tenemos una regla de Lint que marca el uso de componentes de Material 2.
Solo queremos usar Material 3, la versión más reciente y mejor, incluidos los nuevos componentes expresivos que se presentaron el año pasado y este año.
Tenemos una regla de lint personalizada que impide que las personas lean colores de, por ejemplo, archivos en formato XML heredados.
Queremos que Compose sea la primera y única opción para las pantallas nuevas.
Y los elementos de texto componibles, específicamente en el sistema de diseño de Material Design, tienen resguardos para el estilo y el color.
Leen el color del contenido local y la tipografía local, pero no queremos depender de eso.
Queremos que cada texto use de forma intencional el estilo específico, el estilo de tipografía y el color específico de la especificación de diseño.
Esa es una regla de Lint que tenemos.
Y la última, en la línea de ScaffoldPaddingDetector, tenemos una regla de Lint personalizada que aplica que cada función de componibilidad que tenemos use su modificador de entrada.
De nuevo, para evitar que, ya sabes, los nuevos usos se coloquen de forma incorrecta o no respeten el fondo entrante, o lo que sea.
Y estos son los tres puntos principales que nos gustaría que te lleves de esta presentación.
En primer lugar, hoy, a finales de 2026, en nuestra opinión, Compose es un kit de herramientas de IU maduro y probado a gran escala.
Android prioriza Compose.
A principios de este año, en Google I/O, anunciamos que todas las bibliotecas de IU y todos los flujos de agentes se basan en Compose.
Y, como Jon mencionó antes en la presentación, gatea, camina, trota y corre. Te sugerimos que migres de forma incremental, especialmente las bases de código grandes, para acelerar tu velocidad en los flujos de desarrolladores y en los flujos de agentes.
Y, con eso, les agradecemos por compartir su tiempo con nosotros hoy, y en unos minutos, Jon y yo estaremos afuera para responder cualquier pregunta que tengan.
Y, de nuevo, gracias por tu tiempo.
@Composable
fun PurchaseRow(onClick: () -> Unit, modifier: Modifier) {
Row(modifier) {
Text("20% off sale")
if (LocalExpts.current.useButtonV2) {
ButtonV2("Buy", onClick = onClick)
} else {
ButtonV1("Buy", onClick = onClick)
}
}
}