Il componente Navigazione fornisce supporto per le applicazioni Jetpack Compose. Puoi spostarti tra i composabili sfruttando l'infrastruttura e le funzionalità del componente Navigazione.
Configura
Per supportare Compose, utilizza la seguente dipendenza nel file build.gradle
del modulo dell'app:
Alla moda
dependencies { def nav_version = "2.8.0" implementation "androidx.navigation:navigation-compose:$nav_version" }
Kotlin
dependencies { val nav_version = "2.8.0" implementation("androidx.navigation:navigation-compose:$nav_version") }
Inizia
Quando implementi la navigazione in un'app, implementa un host, un grafico e un controllo di navigazione. Per ulteriori informazioni, consulta la panoramica di Navigazione.
Creare un NavController
Per informazioni su come creare un NavController
in Compose, consulta la sezione Compose di Creare un controller di navigazione.
Creare un NavHost
Per informazioni su come creare un NavHost
in Scrivi, consulta la sezione Scrivi
di Creare il grafo di navigazione.
Vai a un composable
Per informazioni sul passaggio a un componibile, consulta Raggiungere una destinazione nella documentazione sull'architettura.
Navigare con gli argomenti
Per informazioni sul passaggio degli argomenti tra le destinazioni composable, consulta la sezione Composizione di Creare il grafico di navigazione.
Recuperare dati complessi durante la navigazione
Ti consigliamo vivamente di non passare oggetti di dati complessi durante la navigazione, ma di passare le informazioni minime necessarie, come un identificatore univoco o un'altra forma di ID, come argomenti quando esegui azioni di navigazione:
// Pass only the user ID when navigating to a new destination as argument
navController.navigate(Profile(id = "user1234"))
Gli oggetti complessi devono essere archiviati come dati in una singola fonte attendibile, ad esempio il livello di dati. Una volta raggiunta la destinazione dopo la navigazione, puoi caricare le informazioni richieste dall'unica fonte attendibile utilizzando l'ID passato. Per recuperare gli argomenti in ViewModel
responsabili dell'accesso al livello dati, utilizza SavedStateHandle
di ViewModel
:
class UserViewModel(
savedStateHandle: SavedStateHandle,
private val userInfoRepository: UserInfoRepository
) : ViewModel() {
private val profile = savedStateHandle.toRoute<Profile>()
// Fetch the relevant user information from the data layer,
// ie. userInfoRepository, based on the passed userId argument
private val userInfo: Flow<UserInfo> = userInfoRepository.getUserInfo(profile.id)
// …
}
Questo approccio consente di evitare la perdita di dati durante le modifiche alla configurazione e eventuali incoerenze durante l'aggiornamento o la modifica dell'oggetto in questione.
Per una spiegazione più approfondita del motivo per cui dovresti evitare di passare dati complessi come argomenti, nonché un elenco dei tipi di argomenti supportati, consulta Trasferire dati tra destinazioni.
Link diretti
La composizione di navigazione supporta i link diretti che possono essere definiti anche all'interno della funzione composable()
. Il parametro deepLinks
accetta un elenco di oggetti
NavDeepLink
che possono essere creati rapidamente utilizzando il metodo
navDeepLink()
:
@Serializable data class Profile(val id: String)
val uri = "https://www.example.com"
composable<Profile>(
deepLinks = listOf(
navDeepLink<Profile>(basePath = "$uri/profile")
)
) { backStackEntry ->
ProfileScreen(id = backStackEntry.toRoute<Profile>().id)
}
Questi link diretti consentono di associare un URL, un'azione o un tipo MIME specifico a un
componibile. Per impostazione predefinita, questi link diretti non sono esposti ad app esterne. Per
rendere disponibili questi link diretti all'esterno, devi aggiungere gli elementi
<intent-filter>
appropriati al file manifest.xml
della tua app. Per attivare il link diretto nell'esempio precedente, devi aggiungere quanto segue all'interno dell'elemento <activity>
del manifest:
<activity …>
<intent-filter>
...
<data android:scheme="https" android:host="www.example.com" />
</intent-filter>
</activity>
La navigazione genera automaticamente link diretti a quel composable quando il link diretto viene attivato da un'altra app.
Questi stessi link diretti possono essere utilizzati anche per creare un PendingIntent
con il link diretti appropriato da un composable:
val id = "exampleId"
val context = LocalContext.current
val deepLinkIntent = Intent(
Intent.ACTION_VIEW,
"https://www.example.com/profile/$id".toUri(),
context,
MyActivity::class.java
)
val deepLinkPendingIntent: PendingIntent? = TaskStackBuilder.create(context).run {
addNextIntentWithParentStack(deepLinkIntent)
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
}
Puoi quindi utilizzare questo deepLinkPendingIntent
come qualsiasi altro PendingIntent
per aprire la tua app nella destinazione del link diretto.
Navigazione nidificata
Per informazioni su come creare grafici di navigazione nidificati, consulta la sezione Grafici nidificati.
Integrazione con la barra di navigazione inferiore
Se definisci NavController
a un livello superiore nella gerarchia componibile, puoi collegare Navigazione ad altri componenti, ad esempio il componente di navigazione in basso. In questo modo puoi navigare selezionando le icone nella barra in basso.
Per utilizzare i componenti BottomNavigation
e BottomNavigationItem
,
aggiungi la dipendenza androidx.compose.material
alla tua applicazione Android.
Alla moda
dependencies { implementation "androidx.compose.material:material:1.7.1" } android { buildFeatures { compose true } composeOptions { kotlinCompilerExtensionVersion = "1.5.15" } kotlinOptions { jvmTarget = "1.8" } }
Kotlin
dependencies { implementation("androidx.compose.material:material:1.7.1") } android { buildFeatures { compose = true } composeOptions { kotlinCompilerExtensionVersion = "1.5.15" } kotlinOptions { jvmTarget = "1.8" } }
Per collegare gli elementi di una barra di navigazione in basso ai percorsi nel grafico di navigazione,
è consigliabile definire una classe, come TopLevelRoute
qui, che abbia
una classe percorso e un'icona.
data class TopLevelRoute<T : Any>(val name: String, val route: T, val icon: ImageVector)
Poi inserisci queste route in un elenco che può essere utilizzato da BottomNavigationItem
:
val topLevelRoutes = listOf(
TopLevelRoute("Profile", Profile, Icons.Profile),
TopLevelRoute("Friends", Friends, Icons.Friends)
)
Nel composable BottomNavigation
, recupera il valore NavBackStackEntry
corrente utilizzando la funzione currentBackStackEntryAsState()
. Questa voce ti consente di accedere
all'NavDestination
attuale. Lo stato selezionato di ogni
BottomNavigationItem
può quindi essere determinato confrontando il percorso dell'elemento con
il percorso della destinazione corrente e le relative destinazioni principali per gestire le richieste
quando utilizzi la navigazione nidificata usando la
gerarchia NavDestination
.
Il percorso dell'elemento viene utilizzato anche per collegare la funzione lambda onClick
a una chiamata a
navigate
in modo che, toccando l'elemento, si possa passare a quell'elemento. Utilizzando i flag saveState
e restoreState
, lo stato e lo stack di ritorno dell'elemento vengono salvati e ripristinati correttamente quando passi da un elemento di navigazione in basso all'altro.
val navController = rememberNavController()
Scaffold(
bottomBar = {
BottomNavigation {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
topLevelRoutes.forEach { topLevelRoute ->
BottomNavigationItem(
icon = { Icon(topLevelRoute.icon, contentDescription = topLevelRoute.name) },
label = { Text(topLevelRoute.name) },
selected = currentDestination?.hierarchy?.any { it.hasRoute(topLevelRoute.route::class) } == true,
onClick = {
navController.navigate(topLevelRoute.route) {
// 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(navController.graph.findStartDestination().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
}
}
)
}
}
}
) { innerPadding ->
NavHost(navController, startDestination = Profile, Modifier.padding(innerPadding)) {
composable<Profile> { ProfileScreen(...) }
composable<Friends> { FriendsScreen(...) }
}
}
In questo caso utilizzi il metodo NavController.currentBackStackEntryAsState()
per aumentare lo stato navController
dalla funzione NavHost
e
condividerlo con il componente BottomNavigation
. Ciò significa che BottomNavigation
avrà automaticamente lo stato più aggiornato.
Interoperabilità
Se vuoi utilizzare il componente Navigation con Compose, hai due opzioni:
- Definisci un grafo di navigazione con il componente Navigazione per i frammenti.
- Definisci un grafo di navigazione con un
NavHost
in Componi utilizzando le destinazioni di Componi. Ciò è possibile solo se tutte le schermate nel grafico di navigazione sono componibili.
Pertanto, per le app miste di Compose e Views consigliamo di utilizzare il componente di navigazione basato su frammenti. I frammenti conterranno quindi schermate basate su visualizzazioni, schermate di composizione e schermate che utilizzano sia le visualizzazioni sia la composizione. Una volta che i contenuti di ogni frammento sono in Compose, il passaggio successivo consiste nel collegare tutte queste schermate a Navigazione in Compose e rimuovere tutti i Frammenti.
Naviga da Scrivi con la navigazione per i frammenti
Per modificare le destinazioni all'interno del codice Compose, esponi gli eventi che possono essere passati e attivati da qualsiasi elemento componibile nella gerarchia:
@Composable
fun MyScreen(onNavigate: (Int) -> Unit) {
Button(onClick = { onNavigate(R.id.nav_profile) } { /* ... */ }
}
Nel frammento, crei il collegamento tra Compose e il componente Navigation basato su frammenti trovando NavController
e passando alla destinazione:
override fun onCreateView( /* ... */ ) {
setContent {
MyScreen(onNavigate = { dest -> findNavController().navigate(dest) })
}
}
In alternativa, puoi passare NavController
alla gerarchia di composizione.
Tuttavia, l'esposizione di funzioni semplici è molto più riutilizzabile e verificabile.
Test
Scollega il codice di navigazione dalle destinazioni dei composabili per consentire di testare ciascun composabile in modo isolato, separatamente dal composabile NavHost
.
Ciò significa che non devi passare navController
direttamente a qualsiasi
componibile, ma passare invece i callback di navigazione come parametri. In questo modo, tutti i tuoi composabili possono essere testati singolarmente, poiché non richiedono un'istanza di navController
nei test.
Il livello di indirizzamento fornito dalla lambda composable
è ciò che ti consente di separare il codice di navigazione dal composable stesso. Questo funziona in due modi:
- Passa solo gli argomenti analizzati nel componibile
- Passa le lambda che devono essere attivate dal composable per navigare, anziché il valore
NavController
stesso.
Ad esempio, un composable ProfileScreen
che accetta un userId
come input e consente agli utenti di accedere alla pagina del profilo di un amico potrebbe avere la firma di:
@Composable
fun ProfileScreen(
userId: String,
navigateToFriendProfile: (friendUserId: String) -> Unit
) {
…
}
In questo modo, il composable ProfileScreen
funziona indipendentemente da Navigation,
consentendo di testarlo in modo indipendente. La funzione lambda composable
incapsulerebbe la logica minima necessaria per colmare il divario tra le API
di navigazione e il componibile:
@Serializable data class Profile(id: String)
composable<Profile> { backStackEntry ->
val profile = backStackEntry.toRoute<Profile>()
ProfileScreen(userId = profile.id) { friendUserId ->
navController.navigate(route = Profile(id = friendUserId))
}
}
Ti consigliamo di scrivere test che coprono i requisiti di navigazione dell'app
testando NavHost
, le azioni di navigazione trasmesse ai componibili, nonché
i singoli componibili della schermata.
Test del NavHost
Per iniziare a testare NavHost
, aggiungi la seguente dipendenza
dei test di navigazione:
dependencies {
// ...
androidTestImplementation "androidx.navigation:navigation-testing:$navigationVersion"
// ...
}
Inserisci NavHost
dell'app in un composable che accetti un NavHostController
come parametro.
@Composable
fun AppNavHost(navController: NavHostController){
NavHost(navController = navController){ ... }
}
Ora puoi testare AppNavHost
e tutta la logica di navigazione definita al suo interno
NavHost
passando un'istanza dell'elemento di test di navigazione
TestNavHostController
. Un test UI che verifica la destinazione iniziale della tua app e NavHost
è simile al seguente:
class NavigationTest {
@get:Rule
val composeTestRule = createComposeRule()
lateinit var navController: TestNavHostController
@Before
fun setupAppNavHost() {
composeTestRule.setContent {
navController = TestNavHostController(LocalContext.current)
navController.navigatorProvider.addNavigator(ComposeNavigator())
AppNavHost(navController = navController)
}
}
// Unit test
@Test
fun appNavHost_verifyStartDestination() {
composeTestRule
.onNodeWithContentDescription("Start Screen")
.assertIsDisplayed()
}
}
Test delle azioni di navigazione
Puoi testare l'implementazione della navigazione in diversi modi, facendo clic sugli elementi dell'interfaccia utente e poi verificando la destinazione visualizzata o confrontando il percorso previsto con quello attuale.
Poiché vuoi testare l'implementazione concreta della tua app, è preferibile fare clic sull'UI. Per scoprire come testarlo insieme a singole funzioni composable in modo isolato, consulta il codelab Test in Jetpack Compose.
Puoi anche utilizzare navController
per controllare le tue affermazioni confrontando il percorso corrente con quello previsto, utilizzando navController
:currentBackStackEntry
@Test
fun appNavHost_clickAllProfiles_navigateToProfiles() {
composeTestRule.onNodeWithContentDescription("All Profiles")
.performScrollTo()
.performClick()
assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<Profile>() ?: false)
}
Per ulteriori indicazioni sulle nozioni di base dei test di Compose, consulta Testare il layout di Compose e il codelab Testing in Jetpack Compose. Per scoprire di più sui test avanzati del codice di navigazione, consulta la guida Testare la navigazione.
Scopri di più
Per scoprire di più su Navigazione Jetpack, consulta la Guida introduttiva all'utilizzo del componente Navigazione o segui il codelab Navigazione Jetpack Compose.
Per scoprire come progettare la navigazione dell'app in modo che si adatti a dimensioni, orientamenti e fattori di forma dello schermo diversi, consulta Navigazione per UI adattabili.
Per scoprire di più su un'implementazione più avanzata della navigazione di Compose in un'app modularizzata, inclusi concetti come i grafici nidificati e l'integrazione della barra di navigazione inferiore, dai un'occhiata all'app Ora su Android su GitHub.
Campioni
Consigliati per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Material Design 2 in Scrivi
- Eseguire la migrazione di Jetpack Navigation a Navigation Compose
- Dove eseguire il hoisting dello stato