Navigation
App Compose con schermate multiple
Laboratorio 2
Programmazione di Sistemi Mobile – AA 2025/2026 1
Recap: Navigazione in Compose
• Un progetto Compose che implementa le funzionalità di
navigazione è dotato di tre componenti:
– Grafo di navigazione: definisce le destinazioni raggiungibili tramite
navigazione all’interno dell’app, indicando per ciascuna il composable
che ne contiene la UI
– NavHost: è il composable utilizzato per l’implementazione del grafo
di navigazione
– NavController: è l’oggetto che gestisce la navigazione, tenendo
traccia della destinazione attuale e fornendo metodi per muoversi tra
le destinazioni definite nel grafo
Programmazione di Sistemi Mobile – AA 2025/2026 2
Esercizi
1. Navigazione in Jetpack Compose
– Con migliore gestione delle stringhe per l'internazionalizzazione
2. TravelDiary - Navigazione
Programmazione di Sistemi Mobile – AA 2025/2026 3
1. Navigazione in Jetpack Compose
• Creare un’app Compose che
permetta di navigare fra tre
schermate, come nella
figura
• Colori:
– Prima schermata:
primaryContainer
– Seconda schermata:
secondaryContainer
– Terza schermata:
tertiaryContainer
Programmazione di Sistemi Mobile – AA 2025/2026 4
1. Ingredienti
1. Composable per l'interfaccia utente
2. Installazione dipendenze
3. Definizione delle destinazioni
4. Creazione del grafo di navigazione
5. Creazione e utilizzo del NavController
Programmazione di Sistemi Mobile – AA 2025/2026 5
1.1. Composable per l'interfaccia utente
• Creare i composable per
le schermate a lato (senza
navigazione)
• Colori:
– Prima schermata:
primaryContainer
– Seconda schermata:
secondaryContainer
– Terza schermata:
tertiaryContainer
Programmazione di Sistemi Mobile – AA 2025/2026 6
Nota: navigazione type safe
• Ci sono due modi per implementare la navigazione in un'app
Jetpack Compose
• Nel primo, un po' come in un sito web, ogni schermata è
identificata da una stringa
composable("home-screen") { HomeScreen() } [Link]("home-screen")
– Benché semplice da implementare, questo metodo presenta un problema:
in fase di navigazione, non siamo in grado di stabilire se la destinazione che
forniamo esista o meno
[Link]("non-existent-screen")
Errore a runtime!
– Questo è particolarmente problematico ed error-prone in caso di rotte con
parametri, in cui è necessario creare la rotta concatenando delle stringhe:
[Link]("profile/$profileId")
Programmazione di Sistemi Mobile – AA 2025/2026 7
Nota: navigazione type safe
• Per ovviare a questo problema, dalla versione 2.8.0 della
libreria Navigation, è possibile implementare la navigazione in
un secondo modo, ossia identificando ogni schermata con un
oggetto
composable<Home> { HomeScreen() } [Link](Profile(id = profileId))
[Link](Home)
[Link](NonExistentScreen)
• Questa è l'implementazione che adotteremo per le
esercitazioni in laboratorio Errore a compile time
Programmazione di Sistemi Mobile – AA 2025/2026 8
1.2. Installazione dipendenze
• Nel file [Link] (modulo :app), aggiungere:
– Nel blocco dependencies
implementation("[Link]:navigation-compose:2.9.7") Replace with version
implementation("[Link]:ktor-serialization-kotlinx-json:2.3.8") catalog entries
– Nel blocco plugins
alias([Link])
• Nel blocco [plugins] del file [Link], aggiungere:
serialization = { id = "[Link]", [Link] = "kotlin" }
Programmazione di Sistemi Mobile – AA 2025/2026 9
1.3. Definizione delle destinazioni
• Ogni destinazione è identificata da un oggetto marcato come
@Serializable, che può essere passato al NavController
per navigare verso la schermata corrispondente
• Per un miglior livello di organizzazione e safety, è consigliabile
definire l’elenco di rotte in una sealed interface o sealed
class:
sealed interface NavigationRoute {
@Serializable data object Screen1 : NavigationRoute
@Serializable data object Screen2 : NavigationRoute
@Serializable data object Screen3 : NavigationRoute
}
Programmazione di Sistemi Mobile – AA 2025/2026 10
1.4. Creazione del grafo di navigazione
@Composable
fun NavGraph(navController: NavHostController) {
NavHost(
navController = navController,
startDestination = NavigationRoute.Screen1
) {
composable<NavigationRoute.Screen1> {
Screen1(navController)
}
composable<NavigationRoute.Screen2> {
Screen2(navController)
}
composable<NavigationRoute.Screen3> {
Screen3(navController)
}
}
}
Programmazione di Sistemi Mobile – AA 2025/2026 11
1.5. Creazione del NavController
val navController = rememberNavController()
Programmazione di Sistemi Mobile – AA 2025/2026 12
Nota: remember
• Una funzione @Composable viene rieseguita ogni volta che i suoi
parametri vengono modificati
• Per questo motivo, è preferibile non effettuare computazioni costose
all’interno dei composable
• Come già accennato in precedenza, la funzione remember permette ai
composable di computare il valore al suo interno solo durante la prima
composition, per poi salvarlo in memoria e restituirlo in tutte le
recomposition successive
val value = remember { someExpensiveFunctionReturningAValue() }
Eseguita solo una volta
Programmazione di Sistemi Mobile – AA 2025/2026 13
Nota: remember
• Il meccanismo di remember è utilizzato dalle funzioni di
moltissime librerie per Jetpack Compose
– La convenzione è di prefissare il nome di queste funzioni con la parola
remember, per indicare l’utilizzo interno di questa feature
• Un’esempio è appunto rememberNavController, che
restituisce un oggetto NavController e ne evita la ricreazione
ad ogni recomposition
val navController = rememberNavController()
Programmazione di Sistemi Mobile – AA 2025/2026 14
1.5. Creazione del NavController
MainActivity
setContent {
NavigationTheme {
val navController = rememberNavController()
NavGraph(navController)
}
}
Programmazione di Sistemi Mobile – AA 2025/2026 15
1.6. Utilizzo del navController
AppBar
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppBar(title: String, navController: NavHostController) {
TopAppBar(
title = { Text(title) },
navigationIcon = {
if ([Link] != null) {
IconButton(onClick = { [Link]() }) {
Icon([Link], "Go Back")
}
}
},
)
}
Programmazione di Sistemi Mobile – AA 2025/2026 16
1.5. Utilizzo del navController
Screen1
@Composable
fun Screen1(navController: NavHostController) {
Scaffold(
topBar = { AppBar("Screen1", navController) }
) { innerPadding ->
Box(
contentAlignment = [Link],
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
.background([Link])
) {
Button(
onClick = { [Link](NavigationRoute.Screen2) },
colors = [Link](
containerColor = [Link],
contentColor = [Link],
),
modifier = [Link]([Link] / 2)
) {
Text("Screen2")
}
}
}
}
Programmazione di Sistemi Mobile – AA 2025/2026 17
1.5. Utilizzo del navController
Screen1
@Composable
fun Screen1(navController: NavHostController) {
Scaffold(
topBar = { AppBar("Screen1", navController) } Naviga alla seconda
) { innerPadding ->
schermata
Box(
contentAlignment = [Link],
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
.background([Link])
) {
Button(
onClick = { [Link](NavigationRoute.Screen2) },
colors = [Link](
containerColor = [Link],
contentColor = [Link],
),
modifier = [Link]([Link] / 2)
) {
Text("Screen2")
}
}
}
}
Programmazione di Sistemi Mobile – AA 2025/2026 18
1.5. Utilizzo del navController
Screen2
@Composable
fun Screen2(navController: NavHostController) {
Scaffold(
topBar = { AppBar(stringResource([Link].screen2_name), navController) }
) { innerPadding ->
Row(
verticalAlignment = [Link],
horizontalArrangement = [Link]([Link]),
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
.background([Link])
.padding([Link])
.fillMaxWidth()
) {
val colors = [Link]([Link], [Link])
Button(
onClick = { [Link]() },
colors = colors,
modifier = [Link](1F)
) { Text(stringResource([Link].screen1_name)) }
Button(
onClick = { [Link](NavigationRoute.Screen3) },
colors = colors,
modifier = [Link](1F)
) { Text(stringResource([Link].screen3_name)) }
}
}
}
Programmazione di Sistemi Mobile – AA 2025/2026 19
1.5. Utilizzo del navController
Screen3
@Composable
fun Screen3(navController: NavHostController) {
Scaffold(
topBar = { AppBar("Screen3", navController) }
) { innerPadding ->
Box(
contentAlignment = [Link],
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
.background([Link])
) {
Button(
onClick = { [Link]() },
colors = [Link](
containerColor = [Link],
contentColor = [Link],
),
modifier = [Link]([Link] / 2)
) {
Text("Screen2")
}
}
}
}
Programmazione di Sistemi Mobile – AA 2025/2026 20
Bonus: gestione delle stringhe e internazionalizzazione
• Possiamo gestire meglio le stringhe all’interno della nostra app
• E aggiungere il supporto multilingua!
Programmazione di Sistemi Mobile – AA 2025/2026 21
Bonus: gestione delle stringhe e internazionalizzazione
1. Nel file res/values/[Link], creiamo una entry per
tutte le stringhe dell’interfaccia utente
<resources>
<string name="app_name">Navigation</string>
<string name="screen1_name">Screen1</string>
<string name="screen2_name">Screen2</string>
<string name="screen3_name">Screen3</string>
<string name="unknown_screen_name">Unknown Screen</string>
<string name="back_button_description">Go Back</string>
</resources>
Programmazione di Sistemi Mobile – AA 2025/2026 22
Bonus: gestione delle stringhe e internazionalizzazione
2. Utilizziamo i valori definiti nel file all’interno dell’app
Esempio:
Text("Screen 2") Text(stringResource([Link].screen2_name))
Programmazione di Sistemi Mobile – AA 2025/2026 23
Bonus: gestione delle stringhe e internazionalizzazione
3. Apriamo l'editor per le traduzioni, aggiungiamo la lingua
italiana e scriviamo le relative stringhe
Programmazione di Sistemi Mobile – AA 2025/2026 24
Bonus: gestione delle stringhe e internazionalizzazione
• È anche possibile creare il file manualmente
<string name="app_name">Navigation</string>
<string name="screen1_name">Schermata1</string>
<string name="screen2_name">Schermata2</string>
<string name="screen3_name">Schermata3</string>
<string name="unknown_screen_name">Schermata Sconosciuta</string>
<string name="back_button_description">Vai Indietro</string>
Programmazione di Sistemi Mobile – AA 2025/2026 25
Bonus: gestione delle stringhe e internazionalizzazione
• Cambiando la lingua del dispositivo in italiano,
cambierà anche il testo all’interno della nostra
app
– Settings → System → Languages → System
Languages → Add a Language
– Selezionare “Italiano (Italia)” e trascinarlo sopra
all’inglese
Programmazione di Sistemi Mobile – AA 2025/2026 26
2. TravelDiary - Navigazione
• Arricchire l’applicazione TravelDiary con le seguenti funzionalità:
– Navigazione tra le varie schermate
– Passaggio del travelId dalla schermata Home a TravelDetails tramite
rotta dinamica
– Bonus: condivisione del viaggio tramite Intent al click del Fab nella
schermata TravelDetails
• Potete partire dal vostro codice, oppure dalla soluzione dello
scorso laboratorio
• Alcuni indizi su come procedere nelle prossime slide
• Link alla documentazione necessaria nell’ultima slide
Programmazione di Sistemi Mobile – AA 2025/2026 27
2. TravelDiary - Navigazione
navigateUp
Intent
Programmazione di Sistemi Mobile – AA 2025/2026 28
Rotte dinamiche
• È possibile passare parametri tra due destinazioni tramite una
rotta dinamica
• Ad esempio, vogliamo passare l’ID del viaggio selezionato
quando navighiamo dalla schermata home a quella di
dettaglio:
– Definiamo una rotta TravelDetails. La rotta assume un valore
diverso in base all'ID del viaggio, pertanto non è un object, ma una
class:
@Serializable
data class TravelDetails(val travelId: String) : TravelDiaryRoute
Programmazione di Sistemi Mobile – AA 2025/2026 29
Rotte dinamiche
• Navigando verso TravelDetails("1234"), visitiamo la rotta
con travelId="1234"
• All'interno della rotta, è possibile reperire il parametro
travelId tramite la backstackEntry
composable<[Link]> { backStackEntry ->
val route = [Link]<[Link]>()
// Your screen here
}
Programmazione di Sistemi Mobile – AA 2025/2026 30
Tocca a voi!
Programmazione di Sistemi Mobile – AA 2025/2026 31
Riferimenti
• Navigation with Compose
[Link]
• Send text content via Intent
[Link]
content
Programmazione di Sistemi Mobile – AA 2025/2026 32