Nous allons maintenant découvrir comment gérer un état purement visuel ou un événement impliquant uniquement de la logique visuelle au sein de Jetpack Compose. Nous nous appuierons à nouveau sur l’écran “SuggestionsScreen”.
Partagez un état ou un événement purement visuel
La même règle s’applique pour les événements impliquant de la logique purement visuelle.
Comment cela s’applique à notre application, BestPodcast ?
Rappelez-vous, dans le chapitre précédent, nous avons identifié un état purement visuel comme la visibilité du bouton permettant de défiler au début de la liste des suggestions (isJumpToTopVisible
).
Nous avons aussi repéré un événement visuel comme l’action de clic sur le bouton permettant de défiler la liste jusqu’à la première suggestion (onJumpToFirstSuggestionClicked
).
Gérez l’étatisJumpToTopVisible
et l’événementonJumpToFirstClicked
Focalisons notre attention sur l’étatisJumpToTopVisible
et l’événement associéonJumpToFirstClicked
.
Cet état dépend de l’étatlistState
de laLazyColumn
utilisée dansSuggestionsList
. Il est associé tout particulièrement à la variablefirstVisibleItem
fournie parlistState
. En effet,isJumpToTopVisible
est vrai silistState.firstVisibleItem
est supérieur à zéro. De même, le clic sur le bouton flottant doit exécuter la fonctionanimateScrollToItem
fournie par l’étatlistState
. Si nous appliquons la règle vue en début de section,SuggestionsList
est un composant adjacent au bouton flottantFloatingActionButton
, il convient donc de gérer cet état et cet événement dans le parent commun le plus bas de l’arbre. C'est-à-dire au niveau de laBox
.
Si nous répercutons cette analyse dans le code, il convient tout d’abord de :
1. Modifier la signature de la fonctionSuggestionsList
pour récupérer l’étatlistState
depuis ses paramètres.
@Composable
fun SuggestionsList(
suggestions: List<Podcast>,
categories: List<Category>,
selectedCategoryId: Int?,
onCategoryClicked: (Int) -> Unit,
onFavouriteToggled: (podcastId: String) -> Unit,
onAddToLibraryClicked: (podcastId: String) -> Unit,
modifier: Modifier = Modifier,
listState: LazyListState = rememberLazyListState(),
) { /*...*/
2. Nous pouvons maintenant instancier listState
depuis laBox
parente et nous en servir pour gérer l’affichage du bouton flottant ainsi que l’action de clic. Le code au sein de laBox
donne alors :
Box( /*... */ ) {
val listState = rememberLazyListState()
val scope = rememberCoroutineScope()
val isJumpToTopVisible = remember {
derivedStateOf { listState.firstVisibleItemIndex > 0 }
}
SuggestionsList(
suggestions = uiState.value.podcasts,
categories = uiState.value.categories,
selectedCategoryId = uiState.value.selectedCategoryId,
listState = listState,
onCategoryClicked = { viewModel.filterByCategory(it) },
onAddToLibraryClicked = { viewModel.togglePodcastInLibrary(it) },
onFavouriteToggled = { viewModel.toggleFavouritePodcast(it) },
)
if (isJumpToTopVisible.value) {
FloatingActionButton(
onClick = {
scope.launch {
listState.animateScrollToItem(0) // onJumpToFirstClicked dans le cours
}
},
modifier = Modifier.padding(bottom = 16.dp)
) {
Icon(
imageVector = Icons.Default.ArrowUpward,
contentDescription = stringResource(R.string.cd_jump_to_first_suggestion)
)
}
}
}
Voici une vidéo qui récapitule les principales étapes pour gérer des états et des événements visuels.
Déclarez un état interne à une fonction composable avec remember
Nous avons vu que pour mémoriser l’état variable d’un composant à l'intérieur d’une fonction composable, il fallait utiliser une fonction remember
. Cette fonction garantit que les modifications de cet état persistent et se reflètent correctement à l'écran. Cette fonction génère un état compréhensible par Jetpack Compose de typeState<T>
. Ainsi tout composant qui lit la valeur de cet état sera automatiquement recomposé si celui-ci change.
Il existe en réalité trois manières d’utiliser la fonctionremember
comme illustré dans ce code :
val text = remember { mutableStateOf("") } // Déclaration 1
var text by remember { mutableStateOf("") } // Déclaration 2
val (text, setText) = remember { mutableStateOf("") } // Déclaration 3
Il n'y a pas de situation spécifique où l'une des syntaxes serait préférée à l'autre. C'est simplement une question de choix personnel. Voyons comment gérer les objets mutables créés par ces 3 déclarations.
Déclaration 1
val text = remember { mutableStateOf("") }
OutlinedTextField(
value = text.value,
onValueChange = {
text.value = it
},
)
Déclaration 2
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
var text by remember { mutableStateOf("") }
OutlinedTextField(
value = text,
onValueChange = {
text = it
},
)
Dans cette version, il est nécessaire d’importer les fonctions nomméessetValue
etgetValue
et d’utiliser le mot clévar
.
Déclaration 3
val (text, setText) = remember { mutableStateOf("") }
OutlinedTextField(
value = text,
onValueChange = {
setText(it)
},
)
Persistez l’état au-delà des changements de configuration
Une particularité de la fonction remember{}
est qu'elle perd sa valeur lors des changements de configuration. Par exemple, si vous utilisez remember{}
pour stocker le texte d'un champ de saisie et que l'utilisateur tourne son téléphone, le contenu de ce champ sera réinitialisé, entraînant la perte de la saisie. Pour éviter ce problème, vous pouvez utiliser rememberSaveable{}
. Cette fonction conserve l'état même après un changement de configuration, tout en offrant les mêmes caractéristiques queremember{}
. Ainsi, pour préserver la saisie de l'utilisateur lors de changements de configuration, utilisez rememberSaveable{}
comme suit :
var text by rememberSaveable { mutableStateOf("") }
Voici une vidéo qui récapitule les principales étapes pour déclarer un état interne à une fonction composable avecremember
et pour persister l’état au-delà des changements de configuration.
À vous de jouer !
Contexte
Alors que Charlie faisait une démonstration de la liste des podcasts à votre product owner, celui-ci a trouvé un autre moyen pour faire défiler automatiquement les suggestions de podcasts en haut de la liste. En effet, il souhaite qu’en cliquant sur le titre "Best Podcasts" dans la barre de navigation en haut de l’écran, la liste des podcasts retourne automatiquement au début de la liste. C’est à vous de développer cette fonctionnalité. Vous décidez avec Charlie d’en profiter pour faire également un point sur les états visuels de l’écran listant les podcasts.
Consignes
1. En tenant compte de cette nouvelle demande du product owner, identifiez les états visuels de l’écran "PodcastsScreen" actuel et complétez dans la documentation du projet la modélisation des états et des événements initiée par Charlie. Voici l’arbre de décision qui a été réalisé avec Excalidraw. Vous pouvez le reproduire dans le logiciel de votre choix afin de l’annoter.
2. Remontez l’étatlistState
au niveau adéquat afin de pouvoir actionner le déroulement de la liste de podcasts lors du clic sur le titre de l’application.
3. Modifiez le texteBest Podcasts
pour le rendre cliquable en utilisant le modifierclickable
et actionner l’événement de défilement de la liste à la position 0.
4. Enfin, avec Charlie, vous avez convenu d’utiliser la syntaxeby remember{}
etby uiState.collectAsStateWithLifecycle()
au sein de l’application. Modifiez le code en conséquence.
Livrables
Votre projet sur Android Studio doit être conforme aux consignes ci-dessus.
En résumé
Un état visuel doit être géré dans la fonction composable qui le concerne ou dans le parent commun le plus bas si partagé entre plusieurs composants.
Les événements purement visuels suivent les mêmes règles que les états visuels et doivent être gérés dans la fonction composable appropriée.
Il existe plusieurs syntaxes utilisant la fonction
remember
et permettant de mémoriser l'état d'un composant au sein d'une fonction composable.Pour maintenir l'état au-delà des changements de configuration, utilisez la fonction
rememberSaveable
.
Il vous manque maintenant deux dernières compétences pour explorer sereinement le développement d’interface avec Jetpack Compose : la gestion des effets de bord et la navigation.