Cedric Dumoulin
Ou comment changer le contenu d’un composant
Comprendre le problème
1 Activity
Le composant principal doit changer en fonction de la navigation
Navigation == click sur un bouton ou lien pour afficher un autre écran
Ici, 3 écrans, dont un avec un paramétre
Activity
Screen 3
Screen 1 Screen 2
Screen 3
Solution
Mécanisme de routeur ou d’aiguillage
Un mécanisme de routeur se charge d’afficher le composant requis
Ce mécanisme est implémenté dans un composant ‘routeur’
Le routeur connait tous les composants affichables
Chaque composant est associé à un id ou un ‘path’
Le routeur choisit le composant
en fonction du path ou de l’id demandé
choix=screen1
Routeur
path=screen1 path=screen2 path=screen3/{year}
Solution
Mécanisme de navigation
Un mécanisme de navigation permet de changer le
path ou l’id
Et donc de changer le composant affiché
Mécanisme
de navigation
choix=screen2
Routeur
path=screen1 path=screen2 path=screen3/{year}
Router avec Compose
Android Compose propose un mécanisme de router et
de navigation :
Router : NavHost
Navigation : [Link]
Navigating with Compose
[Link]
tion#samples
Sélection des composants avec
NavHost Instanciation du
mécanisme de
navigation
val navController = rememberNavController()
Le mécanisme
Composant
de navigation Route de départ
Routeur
associé
NavHost(navController = navController, startDestination = "profile") {
composable("profile") { Profile(/*...*/) }
composable("friendslist") { FriendsList(/*...*/) }
/*...*/
}
Liste des routes:
composable( path ) { Composant() }
Instanciation du NavController
Le NavController permet de faire la navigation
[Link](« destination »);
Il faut l’instancier avant de l’utiliser.
Instanciation dans un des composants parents
Souvent au sommet de l’arbre des composants
Passage aux composants enfants
En tant que parametre
val navController = rememberNavController()
// Create navigation Actions
val navigationActions = remember(navController) {
NavigationActions(navController)
}
AppScaffold(navController, modifier, navigationActions)
Navigation
Ou comment changer le composant sélectionné
Utiliser [Link]( « new route »)
La nouvelle route est mise sur la pile des routes
Pour pouvoir faire un back
On peut manipuler la pile
Pour éviter les doublons, eviter de mettre plusieurs fois la même route
…
Dépile toute les routes jusque
la route de départ.
➔ On sera juste après
[Link](«screen2») { Une seule route de ce type sur
le sommet (si la route est déjà
popUpTo([Link]().id) { au sommet, ne fait rien).
saveState = true
} Indique de récupérer les
launchSingleTop = true valeurs états précedent pour
ce composant
restoreState = true
} Actions exécutées avant
l’empilement de la route
Créer un bouton ou une icone de navigation
Solution avec NavHostController
Passer le NavHostController en parametre
Appeler [Link](«newroute »)
@Composable
fun Screen4(modifier: Modifier = Modifier, navController : NavHostController) {
Column {
Text(text = "Hello the worlds, screen 1")
Button(
onClick = {[Link]("screen2")}
){
Text(text = "See friends list")
}
}
}
Créer une icone de navigation
Solution avec NavHostController
Passer le NavHostController en parametre
Appeler [Link](«newroute »)
IconButton(onClick = {[Link]("screen2")} ) {
Icon(
imageVector = [Link],
contentDescription = "Home"
)
}
Créer un bouton ou une icone de navigation
Solution avec fonction de navigation
Passer une fonction de navigation en paramètre
Utiliser la fonction
NavHost(navController = navController, startDestination = AppDestinations.SCREEN1) {
Déclaration de la fonction et
composable("screen4") {
passage au composant
var onNavigateToScreen2 = {[Link]("screen2")}
Screen4(modifier = modifier, onNavigateToScreen2 = onNavigateToScreen2)
}
@Composable
fun Screen4(modifier: Modifier = Modifier, onNavigateToScreen2: () -> Unit) {
Column {
Fonction de navigation en
Button(onClick = onNavigateToScreen2) { paramètre
Text(text = "See friends list")
} Appel de la fonction de
} navigation
}
Créer un bouton ou une icone de navigation
Solution avec classe NavigationsActions
On regroupe les fonctions de navigation dans une classe
On instancie la classe juste après l’instance du navController
On passe l’instance de la classe aux composants
On peut prévoir plusieurs classes de NavigationActions
Les routes sous forme de
object AppDestinations { constantes
const val SCREEN1 = "screen1"
const val SCREEN2 = "screen2"
}
class NavigationActions(navController: NavHostController) {
val navigateToScreen1: () -> Unit = {
[Link](AppDestinations.SCREEN1) {
// Pop up to the start destination of the graph to avoid building up a large stack of
// destinations on the back stack as users select items
popUpTo([Link]().id) {
saveState = true
}
// Avoid multiple copies of the same destination when reselecting the same item
launchSingleTop = true
// Restore state when reselecting a previously selected item
restoreState = true Les options de gestion de la
} pile
}
Créer un bouton ou une icone de navigation
Solution avec classe NavigationsActions (suite classe)
…
val navigateToScreen2: () -> Unit = {
[Link](AppDestinations.SCREEN2) {
popUpTo([Link]().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
var navigateToScreen3: (year : Int) -> Unit = {year ->
[Link](AppDestinations.SCREEN3 + "/" + year) {
Log.i("cdm", AppDestinations.SCREEN3 + "/" + year)
popUpTo([Link]().id) {
saveState = true
}
launchSingleTop = true Exemple avec passage de
//restoreState = true paramètre au composant
}
}
var navigateToHome: () -> Unit = navigateToScreen1
}
Créer un bouton ou une icone de navigation
Solution avec classe NavigationsActions (instanciation)
fun MainComponent(name: String, modifier: Modifier = Modifier) {
val navController = rememberNavController()
// Create navigation Actions
val navigationActions = remember(navController) { Instanciation de la classe
NavigationActions(navController) NavigationActions après
} l’instanciation du navController
AppScaffold(navController, modifier, navigationActions)
}
Créer un bouton ou une icone de navigation
Solution avec classe NavigationsActions (bouton)
fun AppRouter( Passage de navigationActions en
navController: NavHostController, paramétre
modifier: Modifier,
navigationActions: NavigationActions
){
NavHost(navController = navController, startDestination = AppDestinations.SCREEN1) {
composable(AppDestinations.SCREEN2) {
Screen2(modifier = modifier, navigationActions = navigationActions)
}
} Passage de la classe
@Composable
fun Screen2(modifier: Modifier = Modifier, navigationActions: NavigationActions) {
Column {
Button(onClick = navigationActions.navigateToScreen1) { Utilisation de la classe
Text(text = "See Screen 1")
}
Button(onClick = { navigationActions.navigateToScreen3(2024) })
{ Text(text = "2024") }
Button(onClick = { navigationActions.navigateToScreen3(2020) })
{ Text(text = "2020") }
}
}
Naviguer vers un composant
En lui passant des paramètres de type primitifs
On peut passer des arguments de type primitif au
composant vers lequel on va naviguer
On passe les valeurs dans le path
path/{nonDeLaValeur}
➔on doit pouvoir convertir les valeurs en String
composable( Déclaration du chemin avec une variable ‘year’
AppDestinations.SCREEN3 + "/{year}",
arguments = listOf(navArgument("year") { type = [Link] })
)
Déclaration de la liste des arguments et
{ backStackEntry ->
de leurs type
val year = [Link]?.getInt("year")
Récupération de la valeur de l’argument
Screen3(
modifier = modifier, navigationActions = navigationActions,
year = year )
)
} Passage de l’argument au composant
Naviguer vers un composant
En lui passant des paramètres de type primitifs
Lors de la navigation, il faut construire une URL avec
l’argument dans l’URL
➔ convertir l’argument en String
val year = 2023
[Link](« screen3 » + "/" + year)
Naviguer vers un composant
En lui passant des paramètres de type complexe
Complexe == Class
Ce n’est pas recommandé avec Compose et le Router
[Link]
vigation#retrieving-complex-data
Alternative :
Passer un identifiant de type primitif en paramètre
Dans le composant cible, rechercher l’objet à l’aide de
son identifiant
Recherche dans la DB, sur un serveur …
Arbre des pseudo composants
L’ensemble de vos (pseudo-)composants forme un
arbre
Racine :
Le composant appelé dans onCreate()
MainComponent
AppScaffold
MyTopNavBar MyBottonNavBar AppRouter
{Un des trois}
Screen1 Screen2 Screen3
Problème Barre de navigation
Les applications ont
Contenu de
souvent la page
une barre de navigation en
haut ou en bas, ou les
deux.
Un bouton FAB (Floting
Action Button)
Les barres de navigation
FAB
sont les même pour tous les
écrans
Barre de navigation
Composant spécialisé
On utilise un composant qui prend en charge le
placement et le contrôle :
Des barres de navigation
Du FAB
Du contenu
AppScaffold
MyTopNavBar MyBottonNavBar Contenu FAB
Composant Scaffold
Pour avoir des navbars et un contenu
[Link]
fun AppScaffold(
navController: NavHostController,
modifier: Modifier, Scaffold prend 4
){ arguments :
Scaffold( - topBar
topBar = MyTopBar() - bottomBar
bottomBar = MyBottomBar
floatingActionButton = {
- FAB
FloatingActionButton(onClick = { presses++ }) { - Content
Icon([Link], contentDescription = "Add")
}
}
)
{innerPadding ->
Column(
modifier = [Link](innerPadding)
){
Text(text = « My content")
}
}
NavBar
Pour définir une TopAppBar(
colors = [Link](
barre de navigation ),
containerColor = [Link],
titleContentColor = [Link],
title = {
Text("Top app bar")
},
navigationIcon = {
IconButton(onClick = { /* do something */ }) {
Icon(
imageVector = [Link],
contentDescription = "Go Back"
)
}
},
[Link] actions = {
pack/compose/components/app- IconButton(onClick = { /* do something */ }) {
bars Icon(
TopAppBar imageVector = [Link],
On peut spécifier : contentDescription = "Menu"
Un titre )
Une icone de navigation }
IconButton(onClick = [Link]) {
Des Actions Icon(
… imageVector = [Link],
contentDescription = "Home"
)
}
},
)
NavBar
Pour définir une BottomAppBar(
containerColor = [Link],
contentColor = [Link],
barre de navigation
){
Text(
modifier = Modifier,
textAlign = [Link],
text = "Bottom app bar",
)
IconButton(onClick = [Link]) {
Icon(
imageVector = [Link],
[Link] contentDescription = "Home"
om/jetpack/compose/comp )
onents/app-bars }
Text(
BottomAppBar textAlign = [Link],
On peut spécifier : text = "text2",
)
Un contenu, dans lequel on
}
met des icons de navigation
FAB (Floating Action Button)
FloatingActionButton(onClick = { presses++ }) {
Icon([Link], contentDescription = "Add")
}
Integration Scaffold, Router …
Bien séparer chaque composant :
MainRouter
Et classe RouterActions
MainScaffold
MainTopBar, MainBottomBar, MainFloatingButton
Vous pouvez ajouter MainSliderMenu
Pas vu en TD
MainActivity
MainComponent
MainScaffold
MainTopBar MainBottonBar MainRouter
{Un des trois}
Screen1 Screen2 Screen3
Integration Scaffold, Router …
fun MainComponent(modifier: Modifier = Modifier) {
val navController = rememberNavController()
// Create navigation Actions
val navigationActions = remember(navController) {
fun MainScaffold(
NavigationActions(navController)
navController: NavHostController,
}
modifier: Modifier,
navigationActions : NavigationActions
){ MainScaffold(navController, modifier, navigationActions)
Scaffold( }
topBar = { MainTopBar(navigationActions) }
bottomBar = { MainBottomBar(navigationActions) }
floatingActionButton =
{MainFloatingButton(navigationActions) }
}
)
{innerPadding ->
Column(
modifier = [Link](innerPadding)
){
MainRouter(navigationActions)
}
}
Atelier
Réalisation d’une application ‘multiScreens’ avec :
4 écrans différents (home, screen1 à screen3)
Chaque écran à un bouton permettant de passer à l’écran suivant ou précédent
Un des écrans prend un paramètre de type int, et affiche sa valeur.
Une topbar
Permettant de naviguer vers l’écran home
Une bottomBar
Permettant de naviguer vers l’écran home et screen2
Les écrans doivent être sous la forme de composant, chacun dans un
module
Le composant effectuant le changement d’écran doit être réutilisable
(dans un Composable) et dans un module.
Plus tard nous allons modéliser ce genre d’application
fun MainScaffold(
navController: NavHostController,
modifier: Modifier,
navigationActions : NavigationActions
){
Scaffold(
topBar = { MainTopBar(navigationActions) }
bottomBar = { MainBottomBar(navigationActions) }
floatingActionButton = {MainFloatingButton(navigationActions) }
}
)
{innerPadding ->
Column(
modifier = [Link](innerPadding)
){
MainRouter(navigationActions)
}
}