Essayer Jetpack Compose au combat?

Enfin, le moment est venu où vous n'avez pas besoin de créer Android Studio vous-même pour essayer le nouveau cadre déclaratif de l'interface utilisateur pour Android. Jetpack Compose est désormais disponible en tant que premier aperçu de développement sur le référentiel Maven de Google. Avec cette nouvelle, mon lundi matin a commencé. Et immédiatement, il y avait un désir de voir quel ensemble d'outils ils attendaient.



J'ai décidé de commencer ma connaissance immédiatement avec une tentative de l'introduire dans le projet pour animaux de compagnie publié sur Google Play. De plus, pendant longtemps j'ai voulu faire une page «A propos de l'application». Dans cet article, je vais parler des principaux composants et étapes de connexion de Compose:


  1. Connexion de dépendance
  2. Thèmes et styles. Intégration avec l'existant dans le projet.
  3. Tests d'accessibilité et d'interface utilisateur.
  4. Les principaux composants et analogues des héritiers View.
  5. Travailler avec l'État.

Connexion de dépendance


Pour commencer, j'ai mis à jour le studio de 3.5 à 3.5.1 (en vain), ajouté des dépendances basiques. Une liste complète peut être consultée dans un article 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" } 

Et puis j'ai essayé de collecter tout cela à cause des versions dispersées de Firebase. Après quoi j'ai rencontré des obstacles de composition:


 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) 

Oui, Compose n'était disponible qu'avec minSdk 21 (Lolipop). Il s'agit peut-être d'une mesure temporaire, mais elle devait prendre en charge les versions antérieures du système d'exploitation.


Mais ce n'est pas tout. Compose fonctionne sur Reflection, au lieu du plugin Kotlin Compiler, comme indiqué précédemment, par exemple ici . Par conséquent, pour que tout commence, vous devez également ajouter le Kotlin Reflect en fonction de:


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

Eh bien, pour le dessert. Compose dp implémente des fonctions d'extension pour Int, Long, Float, qui sont marquées avec le mot clé inline. Cela peut provoquer une nouvelle erreur de compilation:


 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 

Pour résoudre, vous devez enregistrer explicitement la version JVM pour Kotlin:


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

Cela semble être tout. Beaucoup plus facile que de construire votre propre studio)


Essayons d'exécuter Hello World (également à partir de l'article de Cyril, mais, contrairement à lui, ajoutez Compose inside Fragment). La disposition du fragment est un FrameLayout vide.


 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 } } } } 

Nous commençons, l'écran suivant se révèle:


image

Du fait que Composable utilise le thème Material par défaut, nous avons obtenu une AppBar violette. Eh bien, et, comme prévu, ce n'est pas du tout conforme au thème sombre de l'application:


image

Essayons de le résoudre.


Thèmes et styles. Intégration avec l'existant dans le projet.


Afin d'utiliser les styles existants à l'intérieur de Composable, nous les passons à l'intérieur du constructeur 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 lui-même se compose de deux parties: MaterialColors et MaterialTypography.
Pour résoudre les couleurs, j'ai utilisé un wrapper sur les styles:


 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 } 

À ce stade, l'AppBar devient verte. Mais pour repeindre le texte, vous devez effectuer une autre action:


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

Le thème est appliqué au widget à l'aide de l'opération unaire plus. Nous le verrons toujours lorsque nous travaillerons avec l'État.


Maintenant, le nouvel écran semble uniforme avec le reste de l'application dans les deux variantes du thème:


image

Compose a également trouvé le fichier DarkTheme.kt dans les sources, dont les fonctions peuvent être utilisées pour déterminer divers déclencheurs pour activer un thème sombre sur Android P et 10.


Tests d'accessibilité et d'interface utilisateur.


Jusqu'à ce que l'écran commence à s'agrandir avec de nouveaux éléments, voyons à quoi il ressemble dans l'inspecteur de disposition et avec l'affichage des bordures des éléments en mode Dev activé:



image

Ici, nous verrons FrameLayout, à l'intérieur duquel seul AndroidComposeView. Les outils existants pour les tests d'acceptabilité et d' interface utilisateur ne sont plus applicables? Peut-être qu'au lieu d'eux, il y aura maintenant une nouvelle bibliothèque: androidx.ui:ui-test .


Les principaux composants et analogues des héritiers View.


Essayons maintenant de rendre l'écran un peu plus informatif. Tout d'abord, modifiez le texte, ajoutez un bouton menant à la page d'application sur Google Play et une image avec un logo. Je vais vous montrer le code tout de suite et ce qui s'est passé:


 @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() }) } } } 

image

Les principes de base de la composition des widgets n'ont pas changé depuis la première apparition des sources Compose .


De l'intéressant:


  • Il n'est pas nécessaire d'annoter les fonctions d'affichage d'éléments individuels avec @Composable.
  • Presque toutes les propriétés des widgets se sont transformées en widgets séparés (Centre au lieu d'Android: gravité, Padding au lieu d'Android: marge, ...)
  • Je n'ai pas pu afficher l'image à partir de dessins.
  • Le paramètre onClick du bouton n'est pas le dernier, car il est impossible de le passer comme lambda sans nom explicite, ce qui semble plus logique:
     Button(“Text"){ openAppInPlayStore() } 

Passons maintenant en revue le ViewGroup existant principal et essayons de trouver des analogues dans Compose.


Au lieu de FrameLayout, vous pouvez utiliser Stack. Ici, tout est simple: les widgets enfants se chevauchent et sont positionnés en fonction de la fonction utilisée pour la pièce jointe: alignés, positionnés ou développés.


LinearLayout est immédiatement remplacé par deux widgets: Column et Row au lieu d'utiliser le paramètre android: orientation. Ils contiennent à leur tour FlexColumn et FlexRow avec une couche de fonction inflexible sur un sous-arbre imbriqué. Eh bien, FlexColumn et FlexRow eux-mêmes sont construits sur Flex avec le paramètre orientation = LayoutOrientation.Vertical ou Horizontal .


Une hiérarchie similaire pour les widgets FlowColumn, FlowRow et Flow. Leur principale différence: si le contenu ne tient pas dans une colonne ou une ligne, la suivante sera dessinée ensuite et les widgets intégrés y «couleront». J'ai du mal à imaginer le véritable objectif de ces widgets.


L'effet ScrollView est obtenu en plaçant une colonne ou une ligne à l'intérieur d'un VerticalScroller ou HorizontalScroller. Ils composent tous les deux à l'intérieur du Scroller, en passant le paramètre isVertical = true ou false .


À la recherche d'un analogue pour ConstraintLayout, ou au moins RelativeLayout est tombé sur un nouveau widget Table. J'ai essayé d'exécuter l'exemple de code dans mon application: DataTableSamples.kt . Mais, comme je n'ai pas essayé de simplifier l'exemple, cela n'a pas fonctionné pour le faire fonctionner.


image

Travailler avec l'État


L'une des innovations les plus attendues du cadre est sa disponibilité opérationnelle pour les architectures unidirectionnelles construites sur la base d'un état unique. Et cela était censé introduire l'annotation @Model dans les classes de balises qui fournissent l'état pour le rendu de l'interface utilisateur.
Prenons un exemple:


 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 ) } } 

Cela crée une classe de données pour le modèle d'état et il n'est pas nécessaire de la marquer avec l'annotation @Model.
L'état initial lui-même est créé à l'intérieur de la fonction @Composable à l'aide de + état.
La visibilité de la boîte de dialogue est déterminée par la propriété visible du modèle obtenue en appelant la propriété value.
Cette propriété peut également être définie sur un nouvel objet immuable, comme cela se produit dans le onClick des deux boutons. Le premier se cache, le second - ferme le dialogue. La boîte de dialogue peut être rouverte en cliquant sur le bouton Ok, défini à l'intérieur de la même fonction @Composable.
Lorsque vous essayez de créer un état en dehors de cette fonction, une erreur se produit:
java.lang.IllegalStateException: Composition requires an active composition context.
Le contexte peut être obtenu en affectant la valeur de la fonction setContent {} dans onCreateView, mais comment l'utiliser, par exemple, dans Presenter ou une autre classe autre que Fragment ou Activity, pour changer l'état n'est pas encore clair.


image

Ceci conclut notre examen de la nouvelle bibliothèque Jetpack Compose. Le cadre justifie son nom architecturalement, en remplaçant tout l'héritage, qui était si gênant dans la hiérarchie des vues, par la composition. Il y a encore trop de questions sur la façon dont les analogues de ViewGroups plus complexes seront mis en œuvre, tels que ConstraintLayout et RecyclerView; documentation et aperçus insuffisants.


Il est absolument clair que Compose n'est pas prêt à l'emploi même dans les petites applications de combat.


Mais ce n'est que la première version de Dev Preview. Il sera intéressant d'observer le développement du concept de collaboration avec l'État et les bibliothèques de la communauté basée sur Compose.


Si vous trouvez des exemples de code plus réussis ou de la documentation pour des cas que je n'ai pas pu obtenir, veuillez écrire dans les commentaires.

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


All Articles