1. Introduction
Compose et le système de vues peuvent fonctionner ensemble.
Dans cet atelier de programmation, vous allez migrer vers Compose certains éléments de l'écran de détails de la plante de Sunflower. Nous avons créé une copie du projet pour vous permettre d'expérimenter la migration d'une application réaliste vers Compose.
À la fin de l'atelier, vous pourrez poursuivre la migration et convertir les autres écrans de Sunflower si vous le souhaitez.
Pour obtenir de l'aide tout au long de cet atelier de programmation, reportez-vous au code suivant :
Objectifs de l'atelier
Cet atelier de programmation traite des points suivants :
- Les différents parcours de migration que vous pouvez suivre
- Comment migrer par incréments une application vers Compose
- Comment ajouter Compose à un écran préexistant créé le système de vues sous Android
- Comment utiliser une vue Android dans Compose
- Comment transposer votre thème du système de vues vers Compose
- Comment tester un écran avec le système de vues et le code Compose
Conditions préalables
- Connaître la syntaxe du langage Kotlin, y compris les lambdas
- Connaître les bases de Compose
Ce dont vous avez besoin
2. Préparer la migration
Vous et votre équipe pouvez choisir comment migrer vers Compose. Il existe de nombreuses façons d'intégrer Jetpack Compose dans une application Android existante. Les deux exemples suivants sont des stratégies de migration courantes :
- Développer un nouvel écran entièrement dans Compose
- Utiliser un écran existant et en migrer progressivement les composants
Compose sur les nouveaux écrans
Lorsqu'il s'agit de refactoriser une application pour l'adapter à une nouvelle technologie, une approche courante consiste à l'adopter dans les nouvelles fonctionnalités créées pour l'application. Dans ce cas, vous créerez de nouveaux écrans. Si vous devez développer un nouvel écran d'interface utilisateur pour votre application, envisagez d'utiliser Compose tout en maintenant le reste de l'application dans le système de vues.
Dans ce second cas, vous établirez une interopérabilité avec Compose en périphérie des fonctionnalités migrées.
Combiner Compose et le système de vues
Certains éléments d'un même écran peuvent être migrés vers Compose tandis que les autres sont maintenus dans le système de vue. Par exemple, vous pouvez migrer une RecyclerView sans transférer le reste de l'écran.
À l'inverse, vous pouvez utiliser Compose comme structure de mise en page pour des vues existantes comme MapView ou AdView, qui ne seraient pas disponibles dans Compose.
Migration complète
Vous pouvez migrer des fragments ou des écrans entiers vers Compose, l'un après l'autre. C'est une approche simple, mais peu précise.
Et dans cet atelier ?
Dans cet atelier de programmation, vous procéderez à la migration par incréments de l'écran des détails de la plante de Sunflower, en combinant Compose et le système de vues. Au terme de l'atelier, vous en saurez suffisamment pour poursuivre la migration, si vous le souhaitez.
3. Configuration
Obtenir le code
Obtenez le code de l'atelier de programmation sur GitHub :
$ git clone https://github.com/googlecodelabs/android-compose-codelabs
Vous pouvez également télécharger le dépôt sous forme de fichier ZIP :
Ouvrir Android Studio
Cet atelier de programmation nécessite Android Studio Bumblebee.
Exécuter l'exemple d'application
Le dépôt que vous venez de télécharger contient du code pour tous les ateliers de programmation traitant de Compose. Pour cet atelier, ouvrez le projet MigrationCodelab
dans Android Studio.
Dans cet atelier, vous allez migrer l'écran des détails de la plante de Sunflower vers Compose. Pour ouvrir l'écran des détails, appuyez sur l'une des plantes disponibles sur l'écran de liste.
Configuration du projet
Le projet comporte plusieurs branches git :
main
est la branche que vous avez extraite ou téléchargée. Il s'agit du point de départ de l'atelier de programmation.end
contient la solution à cet atelier de programmation.
Nous vous recommandons de commencer par le code de la branche main
, puis de suivre l'atelier étape par étape, à votre propre rythme.
Au cours de cet atelier de programmation, vous découvrirez des extraits de code que vous devrez ajouter au projet. À certains endroits, vous devrez également supprimer le code qui est explicitement mentionné dans les commentaires sur les extraits de code.
Pour obtenir la branche end
à l'aide de git, exécutez la commande suivante :
$ git clone -b end https://github.com/googlecodelabs/android-compose-codelabs
Vous pouvez également télécharger le code de la solution en cliquant sur le bouton suivant :
Questions fréquentes
4. Compose dans Sunflower
Compose a déjà été ajouté au code que vous avez téléchargé à partir de la branche main
. Voyons toutefois les éléments nécessaires à son fonctionnement.
Lorsque vous ouvrez le fichier app/build.gradle
(ou build.gradle (Module: compose-migration.app)
), vous noterez que les dépendances de Compose sont importées, ce qui permet à Android Studio d'utiliser Compose avec l'option buildFeatures { compose true }
.
app/build.gradle
android {
...
kotlinOptions {
jvmTarget = '1.8'
useIR = true
}
buildFeatures {
...
compose true
}
composeOptions {
kotlinCompilerExtensionVersion rootProject.composeVersion
}
}
dependencies {
...
// Compose
implementation "androidx.compose.runtime:runtime:$rootProject.composeVersion"
implementation "androidx.compose.ui:ui:$rootProject.composeVersion"
implementation "androidx.compose.foundation:foundation:$rootProject.composeVersion"
implementation "androidx.compose.foundation:foundation-layout:$rootProject.composeVersion"
implementation "androidx.compose.material:material:$rootProject.composeVersion"
implementation "androidx.compose.runtime:runtime-livedata:$rootProject.composeVersion"
implementation "androidx.compose.ui:ui-tooling:$rootProject.composeVersion"
implementation "com.google.android.material:compose-theme-adapter:$rootProject.composeVersion"
...
}
La version de ces dépendances est définie dans le fichier racine build.gradle
.
5. Compose, nous voilà !
Parmi les éléments de l'écran des détails, nous allons migrer la description de la plante vers Compose, sans modifier la structure générale de l'écran. Dans le cas présent, vous suivrez la stratégie de migration Combiner Compose et le système de vues, qui est mentionnée dans la section Préparer votre migration.
Compose a besoin d'une activité ou d'un fragment hôte pour afficher l'interface utilisateur. Comme tous les écrans de Sunflower utilisent des fragments, vous emploierez ComposeView
. Cette vue Android peut héberger du contenu UI de Compose via sa méthode setContent
.
Supprimer le code XML
Commençons par la migration. Ouvrez fragment_plant_detail.xml
et procédez comme suit :
- Passer en vue Code
- Supprimez le code
ConstraintLayout
et lesTextView
s imbriquées dans laNestedScrollView
. L'atelier de programmation compare le code XML et s'y réfère lors de la migration d'éléments individuels. Commenter le code s'avérera utile. - Ajoutez une
ComposeView
avec l'ID de vuecompose_view
pour héberger le code Compose
fragment_plant_detail.xml
<androidx.core.widget.NestedScrollView
android:id="@+id/plant_detail_scrollview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="@dimen/fab_bottom_padding"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
// Step 2) Comment out ConstraintLayout and its children
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/margin_normal">
<TextView
android:id="@+id/plant_detail_name"
...
</androidx.constraintlayout.widget.ConstraintLayout>
// End Step 2) Comment out until here
// Step 3) Add a ComposeView to host Compose code
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.core.widget.NestedScrollView>
Ajouter le code Compose
Vous pouvez désormais commencer à migrer l'écran des détails de la plante vers Compose.
Tout au long de l'atelier de programmation, vous allez ajouter du code Compose au fichier PlantDetailDescription.kt
, qui se trouve dans le dossier plantdetail
. Ouvrez-le. Vous découvrirez le texte "Hello Compose!"
dans un espace réservé à l'avance pour notre projet.
plantdetail/PlantDetailDescription.kt
@Composable
fun PlantDetailDescription() {
Text("Hello Compose")
}
Affichons ce texte sur l'écran en appelant ce composable à partir de la ComposeView
que nous avons ajoutée à l'étape précédente. Ouvrez plantdetail/PlantDetailFragment.kt
.
Comme l'écran utilise une liaison de données, vous pouvez accéder directement à la composeView
et appeler setContent
pour afficher le code Compose sur l'écran. Appelez la composition PlantDetailDescription
dans MaterialTheme
(Sunflower utilise le Material Design).
plantdetail/PlantDetailFragment.kt
class PlantDetailFragment : Fragment() {
...
override fun onCreateView(...): View? {
val binding = DataBindingUtil.inflate<FragmentPlantDetailBinding>(
inflater, R.layout.fragment_plant_detail, container, false
).apply {
...
composeView.setContent {
// You're in Compose world!
MaterialTheme {
PlantDetailDescription()
}
}
}
...
}
}
Si vous exécutez l'application, "Hello Compose!
" s'affiche à l'écran.
6. Créer un composable à partir du XML
Commençons par migrer le nom de la plante. Plus précisément, il s'agira de migrer la TextView
avec l'ID @+id/plant_detail_name
, que vous avez supprimée de fragment_plant_detail.xml
. Voici le code XML correspondant :
<TextView
android:id="@+id/plant_detail_name"
...
android:layout_marginStart="@dimen/margin_small"
android:layout_marginEnd="@dimen/margin_small"
android:gravity="center_horizontal"
android:text="@{viewModel.plant.name}"
android:textAppearance="?attr/textAppearanceHeadline5"
... />
Notez le style textAppearanceHeadline5
, la marge horizontale de 8.dp
et le centrage sur l'axe horizontal de l'écran. Toutefois, le titre à afficher est observé depuis une LiveData
exposée par PlantDetailViewModel
, qui provient de la couche du dépôt.
L'observation de LiveData
sera abordée plus tard. Pour le moment, supposons que nous disposons du nom et le transmettons en tant que paramètre à un nouveau composable PlantName
, que nous créons dans le fichier PlantDetailDescription.kt
. Ce composable sera appelé ultérieurement, à partir du composable PlantDetailDescription
.
PlantDetailDescription.kt
@Composable
private fun PlantName(name: String) {
Text(
text = name,
style = MaterialTheme.typography.h5,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = dimensionResource(R.dimen.margin_small))
.wrapContentWidth(Alignment.CenterHorizontally)
)
}
@Preview
@Composable
private fun PlantNamePreview() {
MaterialTheme {
PlantName("Apple")
}
}
Avec l'aperçu :
Spécifications :
- Le style de
Text
estMaterialTheme.typography.h5
, mappé autextAppearanceHeadline5
du code XML. - Les modificateurs décorent l'élément Text pour refléter sa version XML :
- Le modificateur
fillMaxWidth
correspond àandroid:layout_width="match_parent"
dans le code XML. - Le
padding
horizontal demargin_small
est une valeur issue du système de vues basée sur la fonction d'assistancedimensionResource
. wrapContentWidth
permet d'aligner horizontalement l'élémentText
.
7. ViewModels et LiveData
Transposons maintenant le titre à l'écran. Vous devrez charger les données à l'aide du PlantDetailViewModel
. Pour ce faire, Compose dispose d'intégrations pour ViewModel et LiveData.
ViewModels
Étant donné qu'une instance de PlantDetailViewModel
est utilisée dans le fragment, nous pourrions nous contenter de la transmettre à PlantDetailDescription
en tant que paramètre.
Ouvrez le fichier PlantDetailDescription.kt
et ajoutez le paramètre PlantDetailViewModel
à PlantDetailDescription
:
PlantDetailDescription.kt
@Composable
fun PlantDetailDescription(plantDetailViewModel: PlantDetailViewModel) {
...
}
À présent, transmettez l'instance de ViewModel lorsque ce composable est appelé à partir du fragment :
PlantDetailFragment.kt
class PlantDetailFragment : Fragment() {
...
override fun onCreateView(...): View? {
...
composeView.setContent {
MaterialTheme {
PlantDetailDescription(plantDetailViewModel)
}
}
}
}
LiveData
Ainsi, vous avez déjà accès au champ LiveData<Plant>
du PlantDetailViewModel
pour obtenir le nom de la plante.
Pour observer vos LiveData à partir d'un composable, utilisez la fonction LiveData.observeAsState()
.
Comme les valeurs émises par vos LiveData peuvent être nulles, vous devez encapsuler leur utilisation avec un contrôle des valeurs nulles. Pour cette raison, et pour faciliter la réutilisation, il est judicieux de diviser la consommation des LiveData et d'écouter différents composables. En ce sens, créez un nouveau composable appelé PlantDetailContent
, qui affichera les informations de Plant
.
Compte tenu de ce qui précède, voici à quoi ressemble le fichier PlantDetailDescription.kt
après avoir ajouté l'observation des LiveData.
PlantDetailDescription.kt
@Composable
fun PlantDetailDescription(plantDetailViewModel: PlantDetailViewModel) {
// Observes values coming from the VM's LiveData<Plant> field
val plant by plantDetailViewModel.plant.observeAsState()
// If plant is not null, display the content
plant?.let {
PlantDetailContent(it)
}
}
@Composable
fun PlantDetailContent(plant: Plant) {
PlantName(plant.name)
}
@Preview
@Composable
private fun PlantDetailContentPreview() {
val plant = Plant("id", "Apple", "description", 3, 30, "")
MaterialTheme {
PlantDetailContent(plant)
}
}
L'aperçu est identique à PlantNamePreview
, car PlantDetailContent
n'appelle que PlantName
pour le moment :
Vous avez à présent configuré le ViewModel afin d'afficher un nom de plante dans Compose. Dans les sections suivantes, vous allez créer les autres composables et les relier au ViewModel de la même manière.
8. Plus de migrations de code XML
Il sera désormais plus facile de compléter notre interface utilisateur avec les consignes d'arrosage et la description des plantes. Vous pouvez déjà migrer le reste de l'écran en adoptant la même approche d'observation du code XML que précédemment.
Le code XML correspondant aux consignes d'arrosage, que vous avez supprimé de fragment_plant_detail.xml
, est constitué de deux TextViews associées aux ID plant_watering_header
et plant_watering
.
<TextView
android:id="@+id/plant_watering_header"
...
android:layout_marginStart="@dimen/margin_small"
android:layout_marginTop="@dimen/margin_normal"
android:layout_marginEnd="@dimen/margin_small"
android:gravity="center_horizontal"
android:text="@string/watering_needs_prefix"
android:textColor="?attr/colorAccent"
android:textStyle="bold"
... />
<TextView
android:id="@+id/plant_watering"
...
android:layout_marginStart="@dimen/margin_small"
android:layout_marginEnd="@dimen/margin_small"
android:gravity="center_horizontal"
app:wateringText="@{viewModel.plant.wateringInterval}"
.../>
Procédez comme précédemment et créez un composable appelé PlantWatering
. Ajoutez les éléments Text
pour afficher les consignes d'arrosage à l'écran :
PlantDetailDescription.kt
@Composable
private fun PlantWatering(wateringInterval: Int) {
Column(Modifier.fillMaxWidth()) {
// Same modifier used by both Texts
val centerWithPaddingModifier = Modifier
.padding(horizontal = dimensionResource(R.dimen.margin_small))
.align(Alignment.CenterHorizontally)
val normalPadding = dimensionResource(R.dimen.margin_normal)
Text(
text = stringResource(R.string.watering_needs_prefix),
color = MaterialTheme.colors.primaryVariant,
fontWeight = FontWeight.Bold,
modifier = centerWithPaddingModifier.padding(top = normalPadding)
)
val wateringIntervalText = LocalContext.current.resources.getQuantityString(
R.plurals.watering_needs_suffix, wateringInterval, wateringInterval
)
Text(
text = wateringIntervalText,
modifier = centerWithPaddingModifier.padding(bottom = normalPadding)
)
}
}
@Preview
@Composable
private fun PlantWateringPreview() {
MaterialTheme {
PlantWatering(7)
}
}
Avec l'aperçu :
À noter :
- Comme la marge intérieure horizontale et la décoration d'alignement sont partagées avec les composables
Text
, vous pouvez réutiliser le modificateur en l'attribuant à une variable locale (par exemple,centerWithPaddingModifier
). Les modificateurs sont des objets Kotlin standards, que vous maîtrisez déjà. - L'élément
MaterialTheme
de Compose ne correspond pas exactement aucolorAccent
utilisé dansplant_watering_header
. Nous utiliseronsMaterialTheme.colors.primaryVariant
pour le moment. Vous améliorerez cet aspect dans la section "Interopérabilité des thèmes".
Nous allons connecter tous les éléments et appeler PlantWatering
à partir de PlantDetailContent
. Le code XML ConstraintLayout que nous avons supprimé au début spécifiait une marge de 16.dp
, que nous devons inclure dans notre code Compose.
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/margin_normal">
Dans PlantDetailContent
, créez une Column
pour afficher le nom et les consignes d'arrosage ensemble, et pour intégrer cette marge intérieure. Ajoutez également une Surface
pour obtenir les couleurs de texte et d'arrière-plan appropriées.
PlantDetailDescription.kt
@Composable
fun PlantDetailContent(plant: Plant) {
Surface {
Column(Modifier.padding(dimensionResource(R.dimen.margin_normal))) {
PlantName(plant.name)
PlantWatering(plant.wateringInterval)
}
}
}
Si vous actualisez l'aperçu, le résultat devrait être le suivant :
9. Les vues dans le code Compose
Nous allons maintenant migrer la description de la plante. Le code XML dans fragment_plant_detail.xml
comportait une TextView
spécifiant app:renderHtml="@{viewModel.plant.description}"
pour indiquer quel texte afficher à l'écran. renderHtml
est un adaptateur de liaison. Vous le trouverez dans le fichier PlantDetailBindingAdapters.kt
. La mise en œuvre utilise HtmlCompat.fromHtml
pour placer le texte sur la TextView
.
Toutefois, Compose n'est pas compatible avec les classes Spanned
et ne permet pas d'afficher du texte au format HTML pour le moment. Nous devons donc utiliser une TextView
du système de vues dans le code Compose pour contourner cette limitation.
Comme Compose n'est pas encore en mesure d'afficher le code HTML, vous allez générer automatiquement un fichier TextView
à cet effet, à l'aide de l'API AndroidView
.
AndroidView
accepte une View
en tant que paramètre et vous rappelle lorsque la vue est gonflée.
Commençons par créer notre nouveau composable, PlantDescription
. Ce composable appelle AndroidView
avec la TextView
que nous venons d'enregistrer dans un lambda. Dans le rappel factory
, initialisez une TextView
réagissant aux interactions HTML à l'aide du Context
donné. Et, dans le rappel update
, définissez le texte avec une description au format HTML enregistrée.
PlantDetailDescription.kt
@Composable
private fun PlantDescription(description: String) {
// Remembers the HTML formatted description. Re-executes on a new description
val htmlDescription = remember(description) {
HtmlCompat.fromHtml(description, HtmlCompat.FROM_HTML_MODE_COMPACT)
}
// Displays the TextView on the screen and updates with the HTML description when inflated
// Updates to htmlDescription will make AndroidView recompose and update the text
AndroidView(
factory = { context ->
TextView(context).apply {
movementMethod = LinkMovementMethod.getInstance()
}
},
update = {
it.text = htmlDescription
}
)
}
@Preview
@Composable
private fun PlantDescriptionPreview() {
MaterialTheme {
PlantDescription("HTML<br><br>description")
}
}
Aperçu :
Notez que htmlDescription
conserve la description HTML d'un élément description
transmis en tant que paramètre. Si le paramètre description
est modifié, le code htmlDescription
dans remember
s'exécute à nouveau.
De même, le rappel de mise à jour AndroidView
sera recomposé si htmlDescription
change. Toute lecture d'un état dans le rappel entraîne une recomposition.
Ajoutons PlantDescription
au composable PlantDetailContent
et modifions le code de prévisualisation pour afficher également une description HTML :
PlantDetailDescription.kt
@Composable
fun PlantDetailContent(plant: Plant) {
Surface {
Column(Modifier.padding(dimensionResource(R.dimen.margin_normal))) {
PlantName(plant.name)
PlantWatering(plant.wateringInterval)
PlantDescription(plant.description)
}
}
}
@Preview
@Composable
private fun PlantDetailContentPreview() {
val plant = Plant("id", "Apple", "HTML<br><br>description", 3, 30, "")
MaterialTheme {
PlantDetailContent(plant)
}
}
Avec l'aperçu :
À ce stade, vous avez migré l'ensemble du contenu du fichier ConstraintLayout
d'origine vers Compose. Vous pouvez exécuter l'application pour vérifier qu'elle fonctionne comme prévu.
10. ViewCompositionStrategy
Par défaut, Compose supprime la composition dès lors que la ComposeView
est dissociée d'une fenêtre. Ce n'est pas souhaitable lorsque ComposeView
est utilisé dans des fragments, et ce pour plusieurs raisons :
- Pour conserver l'état, la composition doit suivre le cycle de vie de la vue du fragment pour les types UI
View
de Compose. - Pour maintenir les éléments d'interface utilisateur de Compose à l'écran lors des transitions ou des changements de fenêtre. Pendant les transitions, la
ComposeView
doit rester visible, même après sa dissociation de la fenêtre.
Vous pouvez appeler manuellement la méthode AbstractComposeView.disposeComposition
pour supprimer la composition. Vous pouvez également supprimer automatiquement les compositions qui ne sont plus nécessaires en définissant une autre stratégie, ou en créant la vôtre en appelant la méthode setViewCompositionStrategy
.
Utilisez la stratégie DisposeOnViewTreeLifecycleDestroyed
pour supprimer la composition lorsque le LifecycleOwner
du fragment est détruit.
Comme PlantDetailFragment
comporte des transitions d'entrée et de sortie (reportez-vous à nav_garden.xml
pour en savoir plus), et comme nous utiliserons des types View
dans Compose par la suite, nous devons nous assurer que la ComposeView
applique la stratégie DisposeOnViewTreeLifecycleDestroyed
. Notez que les bonnes pratiques suggèrent de toujours employer cette stratégie lorsque vous utilisez ComposeView
dans des fragments.
plantdetail/PlantDetailFragment.kt
import androidx.compose.ui.platform.ViewCompositionStrategy
...
class PlantDetailFragment : Fragment() {
...
override fun onCreateView(...): View? {
val binding = DataBindingUtil.inflate<FragmentPlantDetailBinding>(
inflater, R.layout.fragment_plant_detail, container, false
).apply {
...
composeView.apply {
// Dispose the Composition when the view's LifecycleOwner
// is destroyed
setViewCompositionStrategy(
ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
)
setContent {
MaterialTheme {
PlantDetailDescription(plantDetailViewModel)
}
}
}
}
...
}
}
11. Interopérabilité des thèmes
Nous avons migré le contenu textuel des détails concernant les plantes vers Compose. Cependant, vous avez peut-être remarqué que Compose ne reflète pas correctement les couleurs du thème. Au lieu d'apparaître en vert, le nom de la plante est en violet.
À ce stade de la migration, vous pouvez faire en sorte que Compose hérite des thèmes disponibles dans le système de vues plutôt que de récréer votre propre thème Material dans Compose. Les thèmes Material sont parfaitement compatibles avec tous les composants Material Design utilisés par Compose.
Pour réutiliser le thème MDC (Material Design Components) du système de vues dans Compose, vous pouvez utiliser l'adaptateur compose-theme-adapter. La fonction MdcTheme
lit automatiquement le thème MDC du contexte hôte et le transmet pour vous à MaterialTheme
, pour le mode clair et le mode sombre. Même si vous ne spécifiez que les couleurs dans le cadre de cet atelier de programmation, la bibliothèque lit également les formes et la typographie du système de vues.
La bibliothèque est déjà incluse dans le fichier app/build.gradle
, comme suit :
...
dependencies {
...
implementation "com.google.android.material:compose-theme-adapter:$rootProject.composeVersion"
...
}
Pour l'utiliser, remplacez les utilisations de MaterialTheme
pour MdcTheme
. Par exemple, dans PlantDetailFragment
:
PlantDetailFragment.kt
class PlantDetailFragment : Fragment() {
...
composeView.apply {
...
setContent {
MdcTheme {
PlantDetailDescription(plantDetailViewModel)
}
}
}
}
Et tous les composables d'aperçu dans le fichier PlantDetailDescription.kt
:
PlantDetailDescription.kt
@Preview
@Composable
private fun PlantDetailContentPreview() {
val plant = Plant("id", "Apple", "HTML<br><br>description", 3, 30, "")
MdcTheme {
PlantDetailContent(plant)
}
}
@Preview
@Composable
private fun PlantNamePreview() {
MdcTheme {
PlantName("Apple")
}
}
@Preview
@Composable
private fun PlantWateringPreview() {
MdcTheme {
PlantWatering(7)
}
}
@Preview
@Composable
private fun PlantDescriptionPreview() {
MdcTheme {
PlantDescription("HTML<br><br>description")
}
}
Comme vous pouvez le voir dans l'aperçu, MdcTheme
récupère les couleurs du thème dans le fichier styles.xml
.
Vous pouvez également prévisualiser l'interface utilisateur en mode sombre en créant une fonction et en transmettant Configuration.UI_MODE_NIGHT_YES
au uiMode
de l'aperçu :
import android.content.res.Configuration
...
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun PlantDetailContentDarkPreview() {
val plant = Plant("id", "Apple", "HTML<br><br>description", 3, 30, "")
MdcTheme {
PlantDetailContent(plant)
}
}
Avec l'aperçu :
Si vous exécutez l'application, celle-ci se comporte exactement comme avant la migration, que ce soit avec le thème clair ou le thème sombre :
12. Test
Après avoir migré certaines parties de l'écran des détails de la plante vers Compose, il est crucial d'effectuer des tests pour vous assurer que tout fonctionne.
Dans Sunflower, le fichier PlantDetailFragmentTest
situé dans le dossier androidTest
teste certaines fonctionnalités de l'application. Ouvrez ce fichier et examinez le code actuel :
testPlantName
vérifie le nom de la plante affichée à l'écrantestShareTextIntent
vérifie que l'intent approprié se déclenche une fois que l'utilisateur appuie sur le bouton de partage
Lorsqu'une activité ou un fragment utilise Compose, au lieu de ActivityScenarioRule
, vous devez utiliser createAndroidComposeRule
, qui intègre une ActivityScenarioRule
avec une ComposeTestRule
pour vous permettre de tester le code Compose.
Dans PlantDetailFragmentTest
, remplacez l'utilisation de ActivityScenarioRule
par createAndroidComposeRule
. Lorsque la règle d'activité est nécessaire pour configurer le test, utilisez l'attribut activityRule
de createAndroidComposeRule
, comme suit :
@RunWith(AndroidJUnit4::class)
class PlantDetailFragmentTest {
@Rule
@JvmField
val composeTestRule = createAndroidComposeRule<GardenActivity>()
...
@Before
fun jumpToPlantDetailFragment() {
populateDatabase()
composeTestRule.activityRule.scenario.onActivity { gardenActivity ->
activity = gardenActivity
val bundle = Bundle().apply { putString("plantId", "malus-pumila") }
findNavController(activity, R.id.nav_host).navigate(R.id.plant_detail_fragment, bundle)
}
}
...
}
Si vous exécutez les tests, testPlantName
échouera. testPlantName
vérifie qu'un élément TextView s'affiche à l'écran. Cependant, vous avez migré cette partie de l'interface utilisateur vers Compose. Vous devez donc utiliser des assertions Compose à la place :
@Test
fun testPlantName() {
composeTestRule.onNodeWithText("Apple").assertIsDisplayed()
}
Si vous exécutez à nouveau les tests, tous les indicateurs devraient être au vert.
13. Félicitations
Bravo ! Vous êtes arrivé au terme de cet atelier de programmation.
La branche compose
du projet GitHub Sunflower d'origine migre complètement l'écran des détails de la plante vers Compose. Outre ce que vous avez fait dans cet atelier de programmation, elle simule également le comportement de CollapsingToolbarLayout, ce qui implique :
- de charger des images avec Compose ;
- des animations ;
- une meilleure gestion des dimensions ;
- et plus encore !
Et maintenant ?
Consultez les autres ateliers de programmation du parcours Compose :
- Principes de base de Compose
- Mises en page avec Compose
- Personnalisation des thèmes avec Compose
- Gérer les états dans Compose
Complément d'informations
- Code accompagné : migration vers Jetpack Compose
- Guide : Compose dans les applications existantes
- Crane, application exemple, intègre un élément MapView dans Compose