Tratando Jetpack Compose en la batalla?

Finalmente, ha llegado el momento en que no necesita crear Android Studio usted mismo para probar el nuevo marco de IU declarativo para Android. Jetpack Compose ahora está disponible como la primera Vista previa del desarrollador en el repositorio Maven de Google. Con esta noticia, comenzó mi lunes por la mañana. E inmediatamente hubo un deseo de ver qué conjunto de herramientas estaban esperando.



Decidí comenzar a conocerme de inmediato con un intento de introducirlo en el proyecto favorito publicado en Google Play. Además, durante mucho tiempo quise hacer una página "Acerca de la aplicación". En este artículo hablaré sobre los componentes principales y los pasos de conexión de Compose:


  1. Conexión de dependencia
  2. Temas y estilos. Integración con existente en el proyecto.
  3. Accesibilidad y pruebas de IU.
  4. Los principales componentes y análogos de los herederos de View.
  5. Trabajar con el estado.

Conexión de dependencia


Para comenzar, actualicé el estudio de 3.5 a 3.5.1 (en vano), agregué dependencias básicas. Se puede ver una lista completa en un artículo de Cyril .


// build.gradle ext.compose_version= '0.1.0-dev01' //build.gradle  dependencies{ ... implementation "androidx.compose:compose-runtime:$compose_version" kapt "androidx.compose:compose-compiler:$compose_version" implementation "androidx.ui:ui-layout:$compose_version" implementation "androidx.ui:ui-android-text:$compose_version" implementation "androidx.ui:ui-text:$compose_version" implementation "androidx.ui:ui-material:$compose_version" } 

Y luego intenté recopilar todo esto debido a las versiones dispersas de Firebase. Después de lo cual me encontré con obstáculos de composición:


 app/src/main/AndroidManifest.xml Error: uses-sdk:minSdkVersion 16 cannot be smaller than version 21 declared in library [androidx.ui:ui-layout:0.1.0-dev01] .../ui-layout-0.1.0-dev01/AndroidManifest.xml as the library might be using APIs not available in 16 Suggestion: use a compatible library with a minSdk of at most 16, or increase this project's minSdk version to at least 21, or use tools:overrideLibrary="androidx.ui.layout" to force usage (may lead to runtime failures) 

Sí, Compose solo estaba disponible con minSdk 21 (Lolipop). Quizás esta sea una medida temporal, pero se esperaba que admitiera versiones anteriores del sistema operativo.


Pero eso no es todo. Compose funciona en Reflection, en lugar del Kotlin Compiler Plugin, como se indicó anteriormente, por ejemplo, aquí . Por lo tanto, para que todo comience, debe agregar Kotlin Reflect también dependiendo de:


 implementation "org.jetbrains.kotlin:kotlin-reflect" 

Bueno, de postre. Compose dp implementa funciones de extensión para Int, Long, Float, que están marcadas con la palabra clave en línea. Esto puede causar un nuevo error de compilación:


 Cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper '-jvm-target' option * https://stackoverflow.com/questions/48988778/cannot-inline-bytecode-built-with-jvm-target-1-8-into-bytecode-that-is-being-bui 

Para resolverlo, debe registrar explícitamente la versión JVM para Kotlin:


 android { … kotlinOptions { jvmTarget = "1.8" } } 

Eso parece ser todo. Mucho más fácil que construir tu propio estudio)


Intentemos ejecutar Hello World (también del artículo de Cyril, pero, a diferencia de él, agregue Compose inside Fragment). El diseño del fragmento es un FrameLayout vacío.


 override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val fragmentView = inflater.inflate(R.layout.fragment_about, container, false) (fragmentView as ViewGroup).setContent { Hello("Jetpack Compose") } return fragmentView } @Composable fun Hello(name: String) = MaterialTheme { FlexColumn { inflexible { // Item height will be equal content height TopAppBar<MenuItem>( // App Bar with title title = { Text("Jetpack Compose Sample") } ) } expanded(1F) { // occupy whole empty space in the Column Center { // Center content Text("Hello $name!") // Text label } } } } 

Comenzamos, resulta la siguiente pantalla:


imagen

Debido al hecho de que Composable usa el tema Material predeterminado, obtuvimos una barra de aplicaciones púrpura. Bueno, y, como se esperaba, no es del todo consistente con el tema oscuro de la aplicación:


imagen

Tratemos de resolverlo.


Temas y estilos. Integración con existente en el proyecto.


Para usar estilos existentes dentro de Composable, los pasamos dentro del constructor MaterialTheme:


 @Composable fun Hello(name: String) = MaterialTheme(colors = MaterialColors( primary = resolveColor(context, R.attr.colorPrimary, MaterialColors().primary), secondary = resolveColor(context, R.attr.colorSecondary, MaterialColors().secondary), onBackground = resolveColor(context, R.attr.textColor, MaterialColors().onBackground) )){...} 

MaterialTheme en sí consta de dos partes: MaterialColors y MaterialTypography.
Para resolver los colores, utilicé un contenedor sobre los estilos:


 private fun resolveColor(context: Context?, @AttrRes attrRes: Int, colorDefault: Color) = context?.let { Color(resolveThemeAttr(it, attrRes).data.toLong()) } ?: colorDefault private fun resolveThemeAttr(context: Context, @AttrRes attrRes: Int): TypedValue { val theme = context.theme val typedValue = TypedValue() theme.resolveAttribute(attrRes, typedValue, true) return typedValue } 

En esta etapa, la barra de aplicaciones se volverá verde. Pero para volver a pintar el texto, debe realizar una acción más:


 Text("Hello $name!", style = TextStyle(color = +themeColor { onBackground })) 

El tema se aplica al widget mediante la operación unaria plus. Todavía lo veremos cuando trabajemos con el Estado.


Ahora la nueva pantalla se ve uniforme con el resto de la aplicación en ambas variantes del tema:


imagen

Compose también encontró el archivo DarkTheme.kt en las fuentes, cuyas funciones se pueden usar para determinar varios desencadenantes para activar un tema oscuro en Android P y 10.


Accesibilidad y pruebas de IU.


Hasta que la pantalla comience a crecer con nuevos elementos, veamos cómo se ve en el Inspector de diseño y con la visualización de los bordes de los elementos en modo Dev activada:



imagen

Aquí veremos FrameLayout, dentro del cual solo AndroidComposeView. ¿Ya no son aplicables las herramientas existentes para las pruebas de Accebility y UI ? Quizás en lugar de ellos haya una nueva biblioteca: androidx.ui:ui-test .


Los principales componentes y análogos de los herederos de View.


Ahora intentemos que la pantalla sea un poco más informativa. Primero, cambie el texto, agregue un botón que conduzca a la página de la aplicación en Google Play y una imagen con un logotipo. Te mostraré el código de inmediato y lo que sucedió:


 @Composable fun AboutScreen() = MaterialTheme(...) { FlexColumn { inflexible { TopAppBar<MenuItem>(title = { Text(getString(R.string.about)) }) } expanded(1F) { VerticalScroller { Column { Image() Title() MyButton() } } } } } private fun Image() { Center { Padding(16.dp) { Container( constraints = DpConstraints( minWidth = 96.dp, minHeight = 96.dp ) ) { imageFromResource(resources, R.drawable.ic_launcher) } } } } private fun Title() { Center { Padding(16.dp) { Text(getString(R.string.app_name) + " " + BuildConfig.VERSION_NAME, style = TextStyle(color = +themeColor { onBackground })) } } } private fun MyButton() { Center { Padding(16.dp) { Button(getString(R.string.about_button), onClick = { openAppInPlayStore() }) } } } 

imagen

Los principios básicos de la composición de widgets no han cambiado desde la primera aparición de las fuentes Compose .


De lo interesante:


  • No es necesario anotar las funciones para mostrar elementos individuales con @Composable.
  • Casi todas las propiedades de los widgets se convirtieron en widgets separados (Centro en lugar de android: gravedad, Relleno en lugar de android: margen, ...)
  • No pude mostrar la imagen de los dibujables.
  • El parámetro onClick del botón no es el último, por lo que es imposible pasarlo como lambda sin un nombre explícito, lo que parecería más lógico:
     Button(“Text"){ openAppInPlayStore() } 

Ahora repasemos el ViewGroup principal existente e intentemos encontrar análogos en Compose.


En lugar de FrameLayout, puedes usar Stack. Aquí todo es simple: los widgets secundarios se superponen y se posicionan según la función utilizada para el archivo adjunto: alineados, posicionados o expandidos.


LinearLayout se reemplaza inmediatamente por dos widgets: Columna y Fila en lugar de usar el parámetro android: orientación. Ellos, a su vez, contienen FlexColumn y FlexRow con una capa de función inflexible sobre un subárbol anidado. Bueno, FlexColumn y FlexRow se basan en Flex con la orientation = LayoutOrientation.Vertical del parámetro orientation = LayoutOrientation.Vertical u Horizontal .


Una jerarquía similar para los widgets FlowColumn, FlowRow y Flow. Su principal diferencia: si el contenido no cabe en una columna o fila, la siguiente se dibujará a continuación, y los widgets incrustados "fluirán" allí. Me resulta difícil imaginar el verdadero propósito de estos widgets.


El efecto ScrollView se logra colocando una Columna o Fila dentro de un VerticalScroller u HorizontalScroller. Ambos componen dentro del Scroller, pasando el parámetro isVertical = true o false .


En busca de un análogo para ConstraintLayout, o al menos RelativeLayout encontró un nuevo widget de tabla. Traté de ejecutar el código de muestra en mi aplicación: DataTableSamples.kt . Pero, como no intenté simplificar el ejemplo, no funcionó para hacerlo funcionar.


imagen

Trabajar con el estado


Una de las innovaciones más esperadas del marco es su disponibilidad inmediata para su uso en arquitecturas unidireccionales construidas sobre la base de un solo estado. Y se suponía que esto introduciría la anotación @Model para etiquetar las clases que proporcionan el estado para representar la interfaz de usuario.
Considere un ejemplo:


 data class DialogVisibleModel(val visible: Boolean, val dismissPushed: Boolean = false) ... @Composable fun SideBySideAlertDialogSample() { val openDialog = +state { DialogVisibleModel(true) } Button(text = "Ok", onClick = { openDialog.value = DialogVisibleModel(true) }) if (openDialog.value.visible) { AlertDialog( onCloseRequest = { // Because we are not setting openDialog.value to false here, // the user can close this dialog only via one of the buttons we provide. }, title = { Text(text = "Title") }, text = { Text("This area typically contains the supportive text" + " which presents the details regarding the Dialog's purpose.") }, confirmButton = { Button("Confirm", onClick = { openDialog.value = DialogVisibleModel(false) }) }, dismissButton = { if (!openDialog.value.dismissPushed) Button("Dismiss", onClick = { openDialog.value = DialogVisibleModel(true, true) }) else { //hidden } }, buttonLayout = AlertDialogButtonLayout.SideBySide ) } } 

Esto crea una clase de datos para el modelo de estado, y no tiene que estar marcado con la anotación @Model.
El estado inicial en sí se crea dentro de la función @Composable usando el estado +.
La visibilidad del diálogo está determinada por la propiedad visible del modelo obtenida llamando a la propiedad value.
Esta propiedad también se puede establecer en un nuevo objeto inmutable, como sucede en onClick de ambos botones. El primero se esconde, el segundo: cierra el diálogo. El cuadro de diálogo se puede volver a abrir haciendo clic en el botón Aceptar, definido dentro de la misma función @Composable.
Al intentar establecer un estado fuera de esta función, se produce un error:
java.lang.IllegalStateException: Composition requires an active composition context.
El contexto se puede obtener asignando el valor de la función setContent {} en onCreateView, pero aún no está claro cómo usarlo, por ejemplo, en Presenter u otra clase que no sea Fragment o Activity, para cambiar el estado.


imagen

Esto concluye nuestra revisión de la nueva biblioteca Jetpack Compose. El marco justifica su nombre arquitectónicamente, reemplazando toda herencia, que era tan inconveniente en la jerarquía de la Vista, con la composición. Todavía hay muchas preguntas sobre cómo se implementarán los análogos de ViewGroups más complejos, como ConstraintLayout y RecyclerView; No hay suficiente documentación y vistas previas.


Está absolutamente claro que Compose no está listo para usarse incluso en pequeñas aplicaciones de combate.


Pero esta es solo la primera versión de Dev Preview. Será interesante observar el desarrollo del concepto de trabajar con el Estado y las bibliotecas de la comunidad en base a Compose.


Si encuentra ejemplos más exitosos de código o documentación para casos que no pude obtener, escriba en los comentarios.

Source: https://habr.com/ru/post/471670/


All Articles