Type-Safe Navigation

Introduction

La majorité des applications Android sont des applications multi-écrans.

Dans Jetpack Compose, la navigation entre écrans repose sur un composable appelé NavHost, qui contient les différentes destinations de l'application. Le NavController gère les interactions de navigation, permettant de passer d'un écran à un autre. Les destinations sont définies à l'aide de la fonction composable(), et les routes peuvent inclure des arguments pour transmettre des données entre les écrans. Le NavController fournit des méthodes comme navigate(), popBackStack(), et navigateUp() pour gérer les transitions et la pile de retour

La première version de la librairie de navigation est assez fastidieuse à utiliser surtout quand il s'agit de naviguer vers un écran en passant des paramètres

A partir de la version 2.8.0 dite "Type-Safe Navigation" publiée en 2024, la syntaxe de la librairie est devenue un peu plus "amicale" grace à la sérialisation du langage Kotlin. C'est cette version qui sera abordée dans ce tutoriel


Préparation

Pour pouvoir utiliser la librairie de navigation, il faut quelques ajustement dans les fichiers du gradle:

  1. Dans le fichier libs.versions.toml, ajouter:

  2. [versions]

    composeNavigation = "2.8.0"
    serialization = "1.6.3"

    [libraries]

    navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "composeNavigation" }
    kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "serialization"}

    [plugins]

    kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }

  3. Dans le fichier build.gradle.kts (Module :app), ajouter:

  4. plugins {

    alias(libs.plugins.kotlin.serialization)
    }

    dependencies {

    implementation(libs.navigation.compose)
    implementation(libs.kotlinx.serialization.core)
    }

  5. Synchroniser le gradle

Mise en œuvre simple

L'implémentation exposée dans ce paragraphe n'est pas conseillée par la documentation officielle car elle consiste à passer le NavController en paramètre aux fonctions qui composent les écrans. Je l'expose tout de même car je la trouve plus facile à mettre en place


  1. Pour chaque écran sans paramètres, déclarer un objet serializable
  2. @Serializable
    object EcranHome
    
    @Serializable
    object EcranA
                        

  3. Pour chaque écran avec paramètres, déclarer une classe serializable
  4. 
    @Serializable
    data class EcranB(
        val param1: String,
        val param2: Int,
        ...
    )
    

  5. Utiliser NavHost() pour définir un graphe de navigation
  6. 
        val myNavController = rememberNavController()
        NavHost(navController = myNavController, startDestination = EcranHome)
        {
            composable <EcranHome> {
                ComposerEcranHome(myNavController)
            }
            composable <EcranA> {
                ComposerEcranA(myNavController)
            }
            
            composable <EcranB> {
                val args = it.toRoute<EcranB>()
                ComposerEcranB(myNavController, args)
            }
        }
                        

  7. Définir les fonctions Composables qui construisent chaque écran.
    • Ecran sans paramètres
    • fun ComposerEcranA(myNavController: NavController) {
              ...
              ...
      }

    • Ecran avec paramètres:
    • fun ComposerEcranB(myNavController: NavController, args: EcranB) {
              ...
              ...
      }


    • Pour naviguer vers un écran sans paramètres:
    • myNavController.navigate(EcranA)

    • Pour naviguer vers un écran avec paramètres:
    • myNavController.navigate(EcranB(param1 = "Dubois", param2 = 25))

    • Pour retourner à l'écran précédent:
    • myNavController.navigateUp()

    • Pour naviguer vers l'écran Home et effacer la pile de retour:
    • myNavController.navigate(EcranHome){
          popUpTo(myNavController.graph.startDestinationId) { inclusive = true }
          launchSingleTop = true
      }

    • Pour accéder à un paramètre: args.param
    • fun ComposerEcranB(myNavController: NavController, args: EcranB) {
              ...
              Text(text = "${args.param1} , ${args.param2}  ans")
              ...
      }

Exemple

Voici un petit exemple constitué de trois écrans. Pour l'instant toute l'application est dans le même fichier. On verra plus tard comment améliorer l'architecture de l'application

Exemple de Navigation
package com.example.navigationtuto1

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Home
import androidx.compose.material3.Button
import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.toRoute
import kotlinx.serialization.Serializable

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Navigateur()
        }
    }
}

@Serializable
object Home

@Serializable
object Clients

@Serializable
data class Profil(
    val name: String,
    val age: Int
)

@Composable
fun Navigateur(){
    val myNavController = rememberNavController()
    NavHost(navController = myNavController, startDestination = Home)
    {
        composable <Home> {
            ScreenHome(myNavController)
        }
        composable <Clients> {
            ScreenClients(myNavController)
        }

        composable <Profil> {
            val profil = it.toRoute<Profil>()
            ScreenProfil(myNavController, profil)
        }
    }
}

@Composable
fun ScreenHome(myNavController: NavController) {
    Column(
        verticalArrangement = Arrangement.spacedBy(40.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier.fillMaxSize()
    ) {
        /*======== Barre de Titre et navigation =========================*/
        Row(
            verticalAlignment = Alignment.CenterVertically,
            modifier = Modifier
                .fillMaxWidth()
                .height(50.dp)
                .background(Color.Blue)
        )
        {
            Text(
                text = "Home",
                fontSize = 30.sp,
                color = Color.White,
                textAlign = TextAlign.Center,
                modifier = Modifier.fillMaxWidth()
            )
        }
        Button(onClick = { myNavController.navigate(Clients) }) {
            Text(text = "Liste des clients", fontSize = 20.sp)
        }
    }
}

@Composable
fun ScreenClients(myNavController: NavController) {
    Column(
        modifier = Modifier.fillMaxSize()
    ) {
        /*======== Barre de Titre et navigation =========================*/
        Row(horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment = Alignment.CenterVertically,
            modifier = Modifier
                .fillMaxWidth()
                .height(50.dp)
                .background(Color.Blue)
        )
        {
            FilledIconButton(onClick = { myNavController.navigateUp() })
            {
                Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
            }
            Text(
                text = "Clients",
                fontSize = 30.sp,
                color = Color.White,
                textAlign = TextAlign.Center,
                modifier = Modifier.fillMaxWidth()
            )
        }
        TextButton(onClick = {  myNavController.navigate(Profil(name = "Dubois", age = 25)) }) {
            Text(text = "Dubois", fontSize = 20.sp)
        }
        TextButton(onClick = {  myNavController.navigate(Profil(name = "Leblanc", age = 35)) }) {
            Text(text = "Leblanc", fontSize = 20.sp)
        }
        TextButton(onClick = {  myNavController.navigate(Profil(name = "Lenoir", age = 45)) }) {
            Text(text = "Lenoir", fontSize = 20.sp)
        }
    }
}

@Composable
fun ScreenProfil(myNavController: NavController, profil: Profil) {
    Column(
        verticalArrangement = Arrangement.spacedBy(40.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier.fillMaxSize()
    ) {
        /*======== Barre de Titre et navigation =========================*/
        Row(horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment = Alignment.CenterVertically,
            modifier = Modifier
                .fillMaxWidth()
                .height(50.dp)
                .background(Color.Blue)
        )
        {
            FilledIconButton(onClick = { myNavController.navigateUp() })
            {
                Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
            }
            Text(
                text = "Profil",
                fontSize = 30.sp,
                color = Color.White,
            )
            FilledIconButton(onClick = { myNavController.navigate(Home){
                popUpTo(myNavController.graph.startDestinationId) { inclusive = true }
                launchSingleTop = true
            } })
            {
                Icon(imageVector = Icons.Default.Home, contentDescription = null)
            }
        }
        Text(text = "Nom:  ${profil.name}", fontSize = 20.sp)
        Text(text = "Age:  ${profil.age}  ans", fontSize = 20.sp)
    }
}

Mise en œuvre recommandée

La documentation officielle recommande de ne pas passer le navcontroller comme paramètre et passer plutôt des événements correspondant à une destination.

Il y a assez peu de changements par rapport à l'implémentation présentée plus haut. Reprenons l'exemple avec trois écrans



  1. Pour chaque écran sans paramètres, déclarer un objet serializable
  2. 
        @Serializable
        object Home
        
        @Serializable
        object Clients
                            

  3. Pour chaque écran avec paramètres, déclarer une classe serializable
  4. 
        @Serializable
        data class Profil(
            val name: String,
            val age: Int,
            ...
        )
        

  5. Utiliser NavHost() pour définir un graphe de navigation
    
            val myNavController = rememberNavController()
            NavHost(navController = myNavController, startDestination = EcranHome)
            {
                // Définir le graphe de navigation ici
            }
                            

    On peut commencer par créer les événements qui décrivent les destinations de navigation
    
        // Navigation vers l'écran précédent
        val navigateBack: () -> Unit = { myNavController.navigateUp() }
    
        // Navigation vers l'écran Home en effaçant la pile de retour
        val navigateHome: () -> Unit = {
            myNavController.navigate(Home) {
                popUpTo(myNavController.graph.startDestinationId) { inclusive = true }
                launchSingleTop = true
            }
        }
    
        // Navigation (sans paramètres) vers l'écran 'Clients'
        val navigateToClients: () -> Unit = { myNavController.navigate(Clients) }
    
        // Navigation (avec paramètres) vers l'écran 'Profil'
        val navigateToProfil: (Profil) -> Unit =  { profil -> myNavController.navigate(profil) }
    

    Ensuite, on peut définir les nœuds du graphe de navigation dans lesquels on précise les fonctions qui construisent les écrans en leur passant les événements de navigation dont ils auront besoin.
        composable<Home> {
            ScreenHome(navigateToClients)
        }
        composable<Clients> {
            ScreenClients(navigateBack,navigateToProfil)
        }
        composable<Profil> {
            val profil = it.toRoute<Profil>() // arguments
            ScreenProfil(navigateBack, navigateHome, profil)
        }

  6. Définir les fonctions Composables qui construisent chaque écran.
    • Ecran Home
    • 
      @Composable
      fun ScreenHome(navigateToClients: () -> Unit) {
          ...
          Button(onClick = navigateToClients) {
              Text(text = "Clients")
          }
          ...
      }
      

    • Ecran Clients
    • 
      @Composable
      fun ScreenClients(navigateBack: () -> Unit, navigateToProfil: (Profil) -> Unit) {
          ...
          TextButton(onClick = { navigateToProfil(Profil("Dubois",25)) }) {
              Text(text = "Dubois",
                  fontSize = 20.sp,
                  )
          }
          ...
      }
      

    • Ecran Profil
    • @Composable
      fun ScreenProfil(navigateBack: () -> Unit, navigateHome: () -> Unit, profil: Profil) {
          ...
          FilledIconButton(onClick = navigateBack)
          {
              Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
          }
          ...
          FilledIconButton(onClick = navigateHome)
          {
              Icon(imageVector = Icons.Default.Home, contentDescription = null)
          }
          ...
          Text(text = "Nom:  ${profil.name}")
          Text(text = "Age:  ${profil.age}  ans")
          ...
      }

Voici le code entier. Tout dans le même fichier.

Navigation
package com.example.navigationts4

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Home
import androidx.compose.material3.Button
import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.toRoute
import kotlinx.serialization.Serializable

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Navigateur()
        }
    }
}

@Serializable
object Home

@Serializable
object Clients

@Serializable
data class Profil(
    val name: String,
    val age: Int,
)


@Composable
fun Navigateur() {
    val myNavController = rememberNavController()
    NavHost(navController = myNavController, startDestination = Home)
    {
        // Navigation vers l'écran précédent
        val navigateBack: () -> Unit = { myNavController.navigateUp() }
        // Navigation vers l'écran Home en effaçant la pile de retour
        val navigateHome: () -> Unit = {
            myNavController.navigate(Home) {
                popUpTo(myNavController.graph.startDestinationId) { inclusive = true }
                launchSingleTop = true
            }
        }
        // Navigation (sans paramètres) vers l'écran 'Clients'
        val navigateToClients: () -> Unit = { myNavController.navigate(Clients) }
        // Navigation (avec paramètres) vers l'écran 'Profil'
        val navigateToProfil: (Profil) -> Unit =  { profil -> myNavController.navigate(profil) }

        composable<Home> {
            ScreenHome(navigateToClients)
        }

        composable<Clients> {
            ScreenClients(navigateBack,navigateToProfil)
        }
        composable<Profil> {
            val profil = it.toRoute<Profil>()  //Arguments
            ScreenProfil(navigateBack, navigateHome, profil)
        }
    }
}

@Composable
fun ScreenHome(navigateToClients: () -> Unit) {
    Column(
        verticalArrangement = Arrangement.spacedBy(40.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier.fillMaxSize()
    ) {
        /*======== Barre de Titre et navigation =========================*/
        Row(
            verticalAlignment = Alignment.CenterVertically,
            modifier = Modifier
                .fillMaxWidth()
                .height(50.dp)
                .background(Color.Blue)
        )
        {
            Text(
                text = "Home",
                fontSize = 30.sp,
                color = Color.White,
                textAlign = TextAlign.Center,
                modifier = Modifier.fillMaxWidth()
            )
        }
        Button(onClick = navigateToClients) {
            Text(text = "Clients")
        }
    }
}


@Composable
fun ScreenClients(navigateBack: () -> Unit, navigateToProfil: (Profil) -> Unit) {

    Column(
        verticalArrangement = Arrangement.spacedBy(10.dp),
//        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier.fillMaxSize()
    ) {
        /*======== Barre de Titre et navigation =========================*/
        Row(
            horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment = Alignment.CenterVertically,
            modifier = Modifier
                .fillMaxWidth()
                .height(50.dp)
                .background(Color.Blue)
        )
        {
            FilledIconButton(onClick = navigateBack)
            {
                Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
            }
            Text(
                text = "Clients",
                fontSize = 30.sp,
                color = Color.White,
                textAlign = TextAlign.Center,
                modifier = Modifier.fillMaxWidth()
            )

        }
        TextButton(onClick = { navigateToProfil(Profil("Dubois",25)) }) {
            Text(text = "Dubois",
                fontSize = 20.sp,
                )
        }
        TextButton(onClick = { navigateToProfil(Profil("Lenoir",30)) }) {
            Text(text = "Lenoir",
                fontSize = 20.sp,
            )
        }
        TextButton(onClick = { navigateToProfil(Profil("Leblanc",45)) }) {
            Text(text = "Leblanc",
                fontSize = 20.sp,
            )
        }
    }
}

@Composable
fun ScreenProfil(navigateBack: () -> Unit, navigateHome: () -> Unit, profil: Profil) {
    Column(
        verticalArrangement = Arrangement.spacedBy(40.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier.fillMaxSize()
    ) {
        /*======== Barre de Titre et navigation =========================*/
        Row(
            horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment = Alignment.CenterVertically,
            modifier = Modifier
                .fillMaxWidth()
                .height(50.dp)
                .background(Color.Blue)
        )
        {
            FilledIconButton(onClick = navigateBack)
            {
                Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
            }
            Text(
                text = "Profil",
                fontSize = 30.sp,
                color = Color.White,
            )
            FilledIconButton(onClick = navigateHome)
            {
                Icon(imageVector = Icons.Default.Home, contentDescription = null)
            }
        }
        Text(text = "Nom:  ${profil.name}")
        Text(text = "Age:  ${profil.age}  ans")
    }
}

Navigation, Implémentation finale

On reprend le code précédent mais on le réorganise en plusieurs fichiers

  • Créer un nouveau projet
  • Compléter les fichiers du gradle comme indiqué dans le paragraphe Préparation
  • Alléger le fichier MainActivity
  • MainActivity.kt
    package com.example.navigationfinal
                    
    import android.os.Bundle
    import androidx.activity.ComponentActivity
    import androidx.activity.compose.setContent
    
    class MainActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                Navigateur()
            }
        }
    }

  • Créer un nouveau fichier pour le navigateur
  • Clic droit sur le package com.example.xxxx → new → Kotlin Class/File → donner un nom de fichier → Clic sur File → Valider par Enter
    Navigateur.kt
    package com.example.navigationfinal
    
    import androidx.compose.runtime.Composable
    import androidx.navigation.compose.NavHost
    import androidx.navigation.compose.composable
    import androidx.navigation.compose.rememberNavController
    import androidx.navigation.toRoute
    import kotlinx.serialization.Serializable
    
    @Serializable
    object Home
    
    @Serializable
    object Clients
    
    @Serializable
    data class Profil(
        val name: String,
        val age: Int,
    )
    
    
    @Composable
    fun Navigateur() {
        val myNavController = rememberNavController()
        NavHost(navController = myNavController, startDestination = Home)
        {
            // Navigation vers l'écran précédent
            val navigateBack: () -> Unit = { myNavController.navigateUp() }
            // Navigation vers l'écran Home en effaçant la pile de retour
            val navigateHome: () -> Unit = {
                myNavController.navigate(Home) {
                    popUpTo(myNavController.graph.startDestinationId) { inclusive = true }
                    launchSingleTop = true
                }
            }
            // Navigation (sans paramètres) vers l'écran 'Clients'
            val navigateToClients: () -> Unit = { myNavController.navigate(Clients) }
            // Navigation (avec paramètres) vers l'écran 'Profil'
            val navigateToProfil: (Profil) -> Unit =  { profil -> myNavController.navigate(profil) }
    
            composable<Home> {
                ScreenHome(navigateToClients)
            }
    
            composable<Clients> {
                ScreenClients(navigateBack,navigateToProfil)
            }
            composable<Profil> {
                val profil = it.toRoute<Profil>()  //Arguments
                ScreenProfil(navigateBack, navigateHome, profil)
            }
        }
    }
    

  • Créer un fichier pour chaque écran
  • ScreenHome.kt
    package com.example.navigationfinal
    
    import androidx.compose.foundation.background
    import androidx.compose.foundation.layout.Arrangement
    import androidx.compose.foundation.layout.Column
    import androidx.compose.foundation.layout.Row
    import androidx.compose.foundation.layout.fillMaxSize
    import androidx.compose.foundation.layout.fillMaxWidth
    import androidx.compose.foundation.layout.height
    import androidx.compose.material3.Button
    import androidx.compose.material3.Text
    import androidx.compose.runtime.Composable
    import androidx.compose.ui.Alignment
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.graphics.Color
    import androidx.compose.ui.text.style.TextAlign
    import androidx.compose.ui.unit.dp
    import androidx.compose.ui.unit.sp
    
    @Composable
    fun ScreenHome(navigateToClients: () -> Unit) {
        Column(
            verticalArrangement = Arrangement.spacedBy(40.dp),
            horizontalAlignment = Alignment.CenterHorizontally,
            modifier = Modifier.fillMaxSize()
        ) {
            /*======== Barre de Titre et navigation =========================*/
            Row(
                verticalAlignment = Alignment.CenterVertically,
                modifier = Modifier
                    .fillMaxWidth()
                    .height(50.dp)
                    .background(Color.Blue)
            )
            {
                Text(
                    text = "Home",
                    fontSize = 30.sp,
                    color = Color.White,
                    textAlign = TextAlign.Center,
                    modifier = Modifier.fillMaxWidth()
                )
            }
            Button(onClick = navigateToClients) {
                Text(text = "Clients")
            }
        }
    }
    

    ScreenClients.kt
    package com.example.navigationfinal
    
    import androidx.compose.foundation.background
    import androidx.compose.foundation.layout.Arrangement
    import androidx.compose.foundation.layout.Column
    import androidx.compose.foundation.layout.Row
    import androidx.compose.foundation.layout.fillMaxSize
    import androidx.compose.foundation.layout.fillMaxWidth
    import androidx.compose.foundation.layout.height
    import androidx.compose.material.icons.Icons
    import androidx.compose.material.icons.automirrored.filled.ArrowBack
    import androidx.compose.material3.FilledIconButton
    import androidx.compose.material3.Icon
    import androidx.compose.material3.Text
    import androidx.compose.material3.TextButton
    import androidx.compose.runtime.Composable
    import androidx.compose.ui.Alignment
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.graphics.Color
    import androidx.compose.ui.text.style.TextAlign
    import androidx.compose.ui.unit.dp
    import androidx.compose.ui.unit.sp
    
    @Composable
    fun ScreenClients(navigateBack: () -> Unit, navigateToProfil: (Profil) -> Unit) {
    
        Column(
            verticalArrangement = Arrangement.spacedBy(10.dp),
            modifier = Modifier.fillMaxSize()
        ) {
            /*======== Barre de Titre et navigation =========================*/
            Row(
                horizontalArrangement = Arrangement.SpaceBetween,
                verticalAlignment = Alignment.CenterVertically,
                modifier = Modifier
                    .fillMaxWidth()
                    .height(50.dp)
                    .background(Color.Blue)
            )
            {
                FilledIconButton(onClick = navigateBack)
                {
                    Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
                }
                Text(
                    text = "Clients",
                    fontSize = 30.sp,
                    color = Color.White,
                    textAlign = TextAlign.Center,
                    modifier = Modifier.fillMaxWidth()
                )
    
            }
            TextButton(onClick = { navigateToProfil(Profil("Dubois",25)) }) {
                Text(text = "Dubois",
                    fontSize = 20.sp,
                )
            }
            TextButton(onClick = { navigateToProfil(Profil("Lenoir",30)) }) {
                Text(text = "Lenoir",
                    fontSize = 20.sp,
                )
            }
            TextButton(onClick = { navigateToProfil(Profil("Leblanc",45)) }) {
                Text(text = "Leblanc",
                    fontSize = 20.sp,
                )
            }
        }
    }
    

    ScreenProfil.kt
    package com.example.navigationfinal
    
    import androidx.compose.foundation.background
    import androidx.compose.foundation.layout.Arrangement
    import androidx.compose.foundation.layout.Column
    import androidx.compose.foundation.layout.Row
    import androidx.compose.foundation.layout.fillMaxSize
    import androidx.compose.foundation.layout.fillMaxWidth
    import androidx.compose.foundation.layout.height
    import androidx.compose.material.icons.Icons
    import androidx.compose.material.icons.automirrored.filled.ArrowBack
    import androidx.compose.material.icons.filled.Home
    import androidx.compose.material3.FilledIconButton
    import androidx.compose.material3.Icon
    import androidx.compose.material3.Text
    import androidx.compose.runtime.Composable
    import androidx.compose.ui.Alignment
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.graphics.Color
    import androidx.compose.ui.unit.dp
    import androidx.compose.ui.unit.sp
    
    @Composable
    fun ScreenProfil(navigateBack: () -> Unit, navigateHome: () -> Unit, profil: Profil) {
        Column(
            verticalArrangement = Arrangement.spacedBy(40.dp),
            horizontalAlignment = Alignment.CenterHorizontally,
            modifier = Modifier.fillMaxSize()
        ) {
            /*======== Barre de Titre et navigation =========================*/
            Row(
                horizontalArrangement = Arrangement.SpaceBetween,
                verticalAlignment = Alignment.CenterVertically,
                modifier = Modifier
                    .fillMaxWidth()
                    .height(50.dp)
                    .background(Color.Blue)
            )
            {
                FilledIconButton(onClick = navigateBack)
                {
                    Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
                }
                Text(
                    text = "Profil",
                    fontSize = 30.sp,
                    color = Color.White,
                )
                FilledIconButton(onClick = navigateHome)
                {
                    Icon(imageVector = Icons.Default.Home, contentDescription = null)
                }
            }
            Text(text = "Nom:  ${profil.name}")
            Text(text = "Age:  ${profil.age}  ans")
        }
    }