El component de navegació fa servir un graf de navegació per gestionar la navegació de la teva aplicació.

El Navigation component utilitza un graf de navegació per gestionar la navegació de la teva aplicació.

El graf de navegació és la estructura de dades que conté cada destinació dins la teva aplicació i la conexió entre elles.

Note: The navigation graph is distinct from the back stack, which is a stack within the NavController that holds destinations the user has recently visited.

Hi ha tres tipus de destinacions: hosted, dialog, i activity.

Les destinacions "hosted" són les que s'utlitzen amb més freqüència, i ocupen tota la pantalla del host.

Hosted destinations are the most common and fundamental destinations.

Project de suport: https://gitlab.com/xtec/kotlin/compose-navigation

Entorn de treball

Crea un nou projecte compose-navigation amb amb Kotlin Multiplatform wizard.

Obre el fitxer composeApp/build.gradle.kts.

En la secció plugins, afegeix el plugin plugin.serialization:


plugins {
    kotlin("plugin.serialization") version "2.1.0"
    // ...
}

En la secció `commonMain.dependencies`, afegeix la dependència amb `navigation-compose` i `kotlinx-serialization-json`:

```kt
dependencies {
  implementation("org.jetbrains.androidx.navigation:navigation-compose:2.8.0-alpha10")
  implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
  // ...
}

A continuació tens una aplicació d'exemple amb dos pantalles ("screens").

Modifica el fitxer composeApp/src/commonMain/kotlin/dev/xtec/App.kt:

package dev.xtec

import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.*

import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import kotlinx.serialization.Serializable


@Composable
fun App() {
    MaterialTheme {
        val controller = rememberNavController()
        NavHost(controller, startDestination = HomeRoute) {
            composable<HomeRoute> { HomeScreen(controller) }
            composable<CityRoute> { CityScreen() }
        }
    }
}

@Serializable
object HomeRoute

@Composable
fun HomeScreen(controller: NavController) {

    Column {
        Text("Home")
        Button(onClick = { controller.navigate(CityRoute) }) {
            Text("City")
        }
    }
}

@Serializable
object CityRoute

@Composable
fun CityScreen() {
    Text("Barcelona")
}

Amb la funció "composable" NavHost crees un graf de navegació.

Al cridar la funció "composable" NavHost passem com a paràmetres un NavController i una ruta pel destí incial (la ruta Home).

Amb la funció composable() defineixes quin "composable" es el destí d'una "ruta".

flowchart LR
    home([HomeRoute]) --> HomeScreen
    city([CityRoute"]) --> CityScreen

Quan defineixes una ruta mai has de dir de quin "screen" ha de venir, sinó quin "screen" a de gestionar la "ruta":

Activitat

1.- Afegeix un nova pàgina CountryScreen.

@Composable
fun App() {
    val controller = rememberNavController()
    NavHost(controller, startDestination = HomeRoute) {
        composable<HomeRoute> { HomeScreen(controller) }
        composable<CityRoute> { CityScreen()
        composable<CountryRoute> { CountryScreen() }
        }
    }
}

@Serializable
object CountryRoute

@Composable
fun CountryScreen() {
    Text("Country")
}

2.- Afegeix un botó a HomeScreen que et porti a CountryScreen.

@Composable
fun HomeScreen(controller: NavController) {

    Column {
        Text("Home")
        Button(onClick = { controller.navigate(CityRoute) }) {
            Text("City")
        }
        Button(onClick = { controller.navigate(CountryRoute) }) {
            Text("Country")
        }
    }
}

3.- Afegeix un botó a CountryScreen que et porti a CiytScreen.

@Composable
fun HomeScreen(controller: NavController) {
    
    Column {
        Text("Home")
        Button(onClick = { controller.navigate(CityRoute) }) {
            Text("City")
        }
        Button(onClick = { controller.navigate(CountryRoute) }) {
            Text("Country")
        }
    }
}

4.- Afegeix una barra de navegació (TopAppBar):

@Composable
fun Nav(controller: NavController) {
    TopAppBar(
        title = { Text("Geography") },
        actions = {
            IconButton(onClick = { controller.navigate(HomeRoute) }) {
                Icon(imageVector = Icons.Filled.Home, contentDescription = "Home")
            }
        }
    )
}

@Composable
fun HomeScreen(controller: NavController) {

    Column {
        Nav(controller)
        Button(onClick = { controller.navigate(CityRoute) }) {
            Text("City")
        }
        Button(onClick = { controller.navigate(CountryRoute) }) {
            Text("Country")
        }
    }
}

// ...

Paràmetres

Si vols passar paràmetres a un "screen" has d'utlitzar un data class enlloc d'un object.

@Serializable
data class CityRoute(val name: String)

Ara el component HomeScreen ja pot utilitzar la nova ruta:

@Composable
fun HomeScreen(controller: NavController) {

        // ...
        Button(onClick = { controller.navigate(CityRoute("Girona")) }) {
            Text("City")
        }
        // ...
    }
}

El NavHost ha de configurar la ruta passant CityRoute a CityScreen:

@Composable
fun App() {
    MaterialTheme {
        val controller = rememberNavController()
        NavHost(controller, startDestination = HomeRoute) {
            composable<HomeRoute> { HomeScreen(controller) }
            composable<CityRoute> { entry -> CityScreen(controller, entry.toRoute()) }
            composable<CountryRoute> { CountryScreen(controller) }
        }
    }
}

El component CityScreen ha d'acceptar com a paràmetre CityRoute:

@Composable
fun CityScreen(controller: NavController, route: CityRoute) {
    Column {
        Nav(controller)
        Text(route.name)
    }
}

Ara CityScreen mostra la ciutat que li passen com a paràmetre.

Activitat

1.- Afegeixt un TextField a HomeScreen on l'usuari pugui posar la ciutat que vulgui i al apretar el botó passi el contigut com a paràmtre

@Composable
fun HomeScreen(controller: NavController) {

        // ...    
        Row(
            modifier = Modifier.padding(8.dp)
        ) {
            TextField(
                value = city.value,
                placeholder = { Text("City Name") },
                onValueChange = { city.value = it })
            Button(
                onClick = { controller.navigate(CityRoute(city.value)) }
            ) {
                Text("City View")
            }
        }
        // ...
    }
}

Activitats

1.- Crea un "screen" Countries que mostri una llista de paisos:

val countries = listOf("Spain", "France", "Italy", "Portugal", "Germany")

@Serializable
object CountryListRoute

@Composable
fun CountryListScreen(controller: NavController) {
    Column {
        Nav(controller)
        LazyColumn() { items(countries) { country -> Text(text = country) } }
    }
}

2.- Afageix un enllaç del "screen" a la barra de navegació

@Composable
fun Nav(controller: NavController) {
    TopAppBar(
        title = { Text("Geography") },
        actions = {
            IconButton(onClick = { controller.navigate(HomeRoute) }) {
                Icon(imageVector = Icons.Filled.Home, contentDescription = "Home")
            }
            IconButton(onClick = { controller.navigate(CountryListRoute) }) {
                Text("Countries")
            }
        }
    )
}

3.- Fes que la llista de països sigui clicable i navegui al "screen" Country:

@Composable
fun CountryListScreen(controller: NavController) {
    Column {
        Nav(controller)
        LazyColumn() {
            items(countries) { country ->
                Row(
                    modifier = Modifier.clickable { controller.navigate(CountryRoute(country)) }
                        .padding(4.dp)
                        .border(1.dp, color = MaterialTheme.colors.primary, CircleShape)
                        .padding(4.dp).fillMaxWidth()
                ) {
                    Text(text = country)
                }
            }
        }
    }
}

4.- Fes el mateix amb un "screen" Cities.

TODO