Navigazione con Scrivi

Il componente Navigation fornisce supporto per le applicazioni Jetpack Compose. Puoi spostarti tra gli elementi composable sfruttando l'infrastruttura e le funzionalità del componente Navigation.

Per la libreria di navigazione in versione pre-release più recente creata appositamente per Compose, consulta la documentazione di Navigation 3.

Configurazione

Per supportare Compose, utilizza la seguente dipendenza nel file build.gradle del modulo dell'app:

Groovy

dependencies {
    def nav_version = "2.9.8"

    implementation "androidx.navigation:navigation-compose:$nav_version"
}

Kotlin

dependencies {
    val nav_version = "2.9.8"

    implementation("androidx.navigation:navigation-compose:$nav_version")
}

Inizia

Quando implementi la navigazione in un'app, implementa un host di navigazione, un grafico e un controller. Per ulteriori informazioni, consulta la panoramica di Navigation.

Per informazioni su come creare un NavController in Compose, consulta la sezione Compose di Crea un controller di navigazione.

Crea un NavHost

Per informazioni su come creare un NavHost in Compose, consulta la sezione Compose di Progetta il grafico di navigazione.

Per informazioni su come passare a un elemento composable, consulta Vai a una destinazione nella documentazione sull'architettura.

Per informazioni sul passaggio di argomenti tra le destinazioni composable, consulta la sezione Compose di Progetta il grafico di navigazione.

Recupera dati complessi durante la navigazione

È vivamente consigliabile non passare oggetti di dati complessi durante la navigazione, ma 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 un'unica fonte di verità, ad esempio il livello dati. Una volta raggiunta la destinazione dopo la navigazione, puoi caricare le informazioni richieste dalla singola fonte di verità utilizzando l'ID passato. Per recuperare gli argomenti nel ViewModel responsabile dell' accesso al livello dati, utilizza SavedStateHandle del 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 aiuta a prevenire la perdita di dati durante le modifiche alla configurazione e qualsiasi incoerenza quando l'oggetto in questione viene aggiornato o modificato.

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 Passare dati tra le destinazioni.

Navigation Compose supporta anche i link diretti che possono essere definiti come parte della funzione composable(). Il parametro deepLinks accetta un elenco di NavDeepLink oggetti che possono essere creati utilizzando il navDeepLink() metodo:

@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 ti consentono di associare un URL, un'azione o un tipo MIME specifico a un elemento composable. Per impostazione predefinita, questi link diretti non sono esposti alle app esterne. Per rendere questi link diretti disponibili esternamente, devi aggiungere gli elementi <intent-filter> appropriati al file manifest.xml dell'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>

Navigation esegue automaticamente il deep linking nell'elemento 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 diretto appropriato da un elemento 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 l'app nella destinazione del link diretto.

Navigazione nidificata

Per informazioni su come creare grafici di navigazione nidificati, consulta Grafici nidificati.

Creare una barra di navigazione e una barra di spostamento adattive

Il NavigationSuiteScaffold mostra l'UI di navigazione di primo livello appropriata per l'app in base all'attuale WindowSizeClass. Per gli schermi compatti, lo scaffold mostra una barra di navigazione in basso; per gli schermi medi ed espansi, una barra di spostamento.

NavigationSuiteScaffold gestisce la navigazione principale; tuttavia, i layout adattivi spesso coinvolgono altri elementi composable specializzati. Per i layout canonici list-detail e riquadro di supporto, comuni nei design adattivi, utilizza ListDetailPaneScaffold e SupportingPaneScaffold, rispettivamente. Per ulteriori informazioni, consulta Crea layout adattivi.

Interoperabilità

Se vuoi utilizzare il componente Navigation con Compose, hai due opzioni:

  • Definisci un grafico di navigazione con il componente Navigation per i fragment.
  • Definisci un grafico di navigazione con un NavHost in Compose utilizzando le destinazioni di Compose. Questa operazione è possibile solo se tutte le schermate del grafico di navigazione sono elementi composable.

Pertanto, per le app miste Compose e Views, è consigliabile utilizzare il componente Navigation basato su fragment. I fragment conterranno quindi schermate basate su View, schermate Compose e schermate che utilizzano sia View che Compose. Una volta che i contenuti di ogni fragment sono in Compose, il passaggio successivo consiste nel collegare tutte queste schermate con Navigation Compose e rimuovere tutti i fragment.

Per modificare le destinazioni all'interno del codice Compose, esponi gli eventi che possono essere passati e attivati da qualsiasi elemento composable nella gerarchia:

@Composable
fun MyScreen(onNavigate: (Int) -> Unit) {
    Button(onClick = { onNavigate(R.id.nav_profile) } { /* ... */ }
}

Nel fragment, crei il ponte tra Compose e il componente Navigation basato su fragment trovando NavController e passando alla destinazione:

override fun onCreateView( /* ... */ ) {
    setContent {
        MyScreen(onNavigate = { dest -> findNavController().navigate(dest) })
    }
}

In alternativa, puoi passare NavController alla gerarchia di Compose. Tuttavia, l'esposizione delle funzioni è molto più riutilizzabile e testabile.

Test

Disaccoppia il codice di navigazione dalle destinazioni composable per consentire il test di ogni elemento composable in isolamento, separatamente dall'elemento composable NavHost.

Ciò significa che non devi passare navController direttamente a nessun elemento composable, ma devi passare i callback di navigazione come parametri. In questo modo, tutti gli elementi composable possono essere testati singolarmente, poiché non richiedono un'istanza di navController nei test.

Il livello di indirezione fornito dalla lambda composable consente di separare il codice di navigazione dall'elemento composable stesso. Funziona in due direzioni:

  • Passa solo gli argomenti analizzati all'elemento composable.
  • Passa le lambda che devono essere attivate dall'elemento composable per navigare, anziché NavController stesso.

Ad esempio, un elemento composable ProfileScreen che accetta un userId come input e consente agli utenti di passare alla pagina del profilo di un amico potrebbe avere la seguente firma:

@Composable
fun ProfileScreen(
    userId: String,
    navigateToFriendProfile: (friendUserId: String) -> Unit
) {
 
}

In questo modo, l'elemento composable ProfileScreen funziona indipendentemente da Navigation, consentendo di testarlo in modo indipendente. La lambda composable incapsulerebbe la logica minima necessaria per colmare il divario tra le API Navigation e l'elemento composable:

@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 coprano i requisiti di navigazione dell'app testando NavHost, le azioni di navigazione passate agli elementi componibili e i singoli elementi componibili dello schermo.

Test di NavHost

Per iniziare a testare NavHost , aggiungi la seguente dipendenza di test di navigazione:

dependencies {
// ...
  androidTestImplementation "androidx.navigation:navigation-testing:$navigationVersion"
  // ...
}

Inserisci NavHost dell'app in un elemento componibile che accetta un NavHostController come parametro.

@Composable
fun AppNavHost(navController: NavHostController){
  NavHost(navController = navController){ ... }
}

Ora puoi testare AppNavHost e tutta la logica di navigazione definita all'interno NavHost passando un'istanza dell'artefatto di test di navigazione TestNavHostController. Un test dell'UI che verifica la destinazione iniziale dell'app e NavHost avrebbe il seguente aspetto:

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, eseguendo clic sugli elementi dell'UI e verificando la destinazione visualizzata o confrontando il percorso previsto con il percorso attuale.

Poiché vuoi testare l'implementazione concreta dell'app, sono preferibili i clic sull'UI. Per scoprire come testare questa funzionalità insieme alle singole funzioni composable in isolamento, consulta il codelab Test in Jetpack Compose.

Puoi anche utilizzare navController per controllare le asserzioni confrontando il percorso attuale con quello previsto, utilizzando currentBackStackEntry di navController:

@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 Test in Jetpack Compose codelab. Per saperne di più sui test avanzati del codice di navigazione, consulta la guida Test di navigazione.

Scopri di più

Per saperne di più su Jetpack Navigation, consulta Guida introduttiva al componente Navigation o segui il codelab Jetpack Compose Navigation.

Per scoprire come progettare la navigazione dell'app in modo che si adatti a diverse dimensioni dello schermo , orientamenti e fattori di forma, consulta Navigazione per UI responsive.

Per scoprire di più su un'implementazione di navigazione Compose più avanzata in un'app modularizzata, inclusi concetti come grafici nidificati e integrazione della barra di navigazione in basso, dai un'occhiata all'app Now in Android su GitHub.

Esempi