Jetpack Navigation zu Navigation Compose migrieren

Mit der Navigation Compose API können Sie in einer Compose-App zwischen Composables navigieren und dabei die Komponente, Infrastruktur und Funktionen von Jetpack Navigation nutzen.

Auf dieser Seite wird beschrieben, wie Sie von einer fragmentbasierten Jetpack Navigation zu Navigation Compose migrieren. Dies ist Teil der größeren, ansichtsbasierten UI-Migration zu Jetpack Compose.

Voraussetzungen für die Migration

Sie können zu Navigation Compose migrieren, sobald Sie alle Ihre Fragmente durch entsprechende Bildschirm-Composables ersetzt haben. Bildschirm-Composables können eine Mischung aus Compose- und Ansichtsinhalten enthalten, aber alle Navigationsziele müssen Composables sein, um die Migration zu Navigation Compose zu ermöglichen. Bis dahin sollten Sie die fragmentbasierte Navigationskomponente weiterhin in Ihrem Interop-View- und Compose-Code verwenden. Weitere Informationen finden Sie in der Dokumentation zur Navigation-Interop.

Migrationsschritte

Unabhängig davon, ob Sie unserer empfohlenen Migrationsstrategie folgen oder einen anderen Ansatz wählen, erreichen Sie einen Punkt, an dem alle Navigationsziele Bildschirm-Composables sind und Fragmente nur als Composables-Container fungieren. An diesem Punkt können Sie zu Navigation Compose migrieren.

Wenn Ihre App bereits einem UDF-Designmuster und unserem Leitfaden zur Architektur folgt, sollten für die Migration zu Jetpack Compose und Navigation Compose keine größeren Refaktorierungen anderer Ebenen Ihrer App erforderlich sein, abgesehen von der UI-Ebene.

So migrieren Sie zu Navigation Compose:

  1. Fügen Sie Ihrer App die Navigation Compose-Abhängigkeit hinzu.
  2. Erstellen Sie ein App-level-Composable und fügen Sie es Ihrer Activity als Compose-Einstiegspunkt hinzu. Ersetzen Sie dabei die Einrichtung des Ansichtslayouts:

    class SampleActivity : ComponentActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            // setContentView<ActivitySampleBinding>(this, R.layout.activity_sample)
            setContent {
                SampleApp(/* ... */)
            }
        }
    }

  3. Erstellen Sie Typen für jedes Navigationsziel. Verwenden Sie ein data object für Ziele, für die keine Daten erforderlich sind, und data class oder class für Ziele, für die Daten erforderlich sind.

    @Serializable data object First
    @Serializable data class Second(val id: String)
    @Serializable data object Third
    

  4. Richten Sie den NavController an einer Stelle ein, an der alle Composables, die darauf verweisen müssen, Zugriff darauf haben (normalerweise in Ihrem App Composable). Dieser Ansatz folgt den Prinzipien des State Hoisting und ermöglicht es Ihnen, den NavController als Single Source of Truth für die Navigation zwischen Composables-Bildschirmen und die Verwaltung des Back Stacks zu verwenden:

    @Composable
    fun SampleApp() {
        val navController = rememberNavController()
        // ...
    }

  5. Erstellen Sie den NavHost Ihrer App im App Composable und übergeben Sie den navController:

    @Composable
    fun SampleApp() {
        val navController = rememberNavController()
    
        SampleNavHost(navController = navController)
    }
    
    @Composable
    fun SampleNavHost(
        navController: NavHostController
    ) {
        NavHost(navController = navController, startDestination = First) {
            // ...
        }
    }

  6. Fügen Sie die composable-Ziele hinzu, um den Navigationsgraphen zu erstellen. Wenn jeder Bildschirm bereits zu Compose migriert wurde, besteht dieser Schritt nur darin, diese Bildschirm-Composables aus Ihren Fragmenten in die composable-Ziele zu extrahieren:

    class FirstFragment : Fragment() {
    
        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View {
            return ComposeView(requireContext()).apply {
                setContent {
                    // FirstScreen(...) EXTRACT FROM HERE
                }
            }
        }
    }
    
    @Composable
    fun SampleNavHost(
        navController: NavHostController
    ) {
        NavHost(navController = navController, startDestination = First) {
            composable<First> {
                FirstScreen(/* ... */) // EXTRACT TO HERE
            }
            composable<Second> {
                SecondScreen(/* ... */)
            }
            // ...
        }
    }

  7. Wenn Sie der Anleitung zur Architektur Ihrer Compose-UI gefolgt sind, insbesondere wie ViewModels und Navigationsereignisse an Composables übergeben werden sollten, besteht der nächste Schritt darin, die Bereitstellung des ViewModel für jedes Bildschirm-Composable zu ändern. Sie können häufig die Hilt-Injection und den zugehörigen Integrations punkt mit Compose und Navigation über hiltViewModel verwenden:

    @Composable
    fun FirstScreen(
        // viewModel: FirstViewModel = viewModel(),
        viewModel: FirstViewModel = hiltViewModel(),
        onButtonClick: () -> Unit = {},
    ) {
        // ...
    }

  8. Ersetzen Sie alle findNavController()-Navigationsaufrufe durch die navController-Aufrufe und übergeben Sie diese als Navigationsereignisse an jeden komponierbaren Bildschirm, anstatt den gesamten navController zu übergeben. Dieser Ansatz folgt den best practices für das Bereitstellen von Ereignissen aus Composables-Funktionen für Aufrufer und behält den navController als Single Source of Truth bei.

    Daten können an ein Ziel übergeben werden, indem eine Instanz der für dieses Ziel definierten Routenklasse erstellt wird. Sie können dann entweder direkt aus dem Back Stack-Eintrag am Ziel oder aus einem ViewModel mit SavedStateHandle.toRoute() abgerufen werden.

    @Composable
    fun SampleNavHost(
        navController: NavHostController
    ) {
        NavHost(navController = navController, startDestination = First) {
            composable<First> {
                FirstScreen(
                    onButtonClick = {
                        // findNavController().navigate(firstScreenToSecondScreenAction)
                        navController.navigate(Second(id = "ABC"))
                    }
                )
            }
            composable<Second> { backStackEntry ->
                val secondRoute = backStackEntry.toRoute<Second>()
                SecondScreen(
                    id = secondRoute.id,
                    onIconClick = {
                        // findNavController().navigate(secondScreenToThirdScreenAction)
                        navController.navigate(Third)
                    }
                )
            }
            // ...
        }
    }

  9. Entfernen Sie alle Fragmente, relevanten XML-Layouts, unnötigen Navigations- und anderen Ressourcen sowie veralteten Fragment- und Jetpack Navigation-Abhängigkeiten.

Dieselben Schritte mit weiteren Details zu Navigation Compose finden Sie in der Einrichtungsdokumentation.

Gängige Anwendungsfälle

Unabhängig davon, welche Navigationskomponente Sie verwenden, gelten die gleichen Navigationsprinzipien.

Zu den häufigsten Anwendungsfällen bei der Migration gehören:

Weitere Informationen zu diesen Anwendungsfällen finden Sie unter Navigieren mit Compose.

Komplexe Daten beim Navigieren abrufen

Wir empfehlen dringend, beim Navigieren keine komplexen Datenobjekte zu übergeben. Übergeben Sie stattdessen die mindestens erforderlichen Informationen, z. B. eine eindeutige ID oder eine andere Form von ID, als Argumente, wenn Sie Navigationsaktionen ausführen. Komplexe Objekte sollten als Daten in einer Single Source of Truth gespeichert werden, z. B. in der Daten schicht. Weitere Informationen finden Sie unter Komplexe Daten beim Navigieren abrufen.

Wenn Ihre Fragmente komplexe Objekte als Argumente übergeben, sollten Sie zuerst Ihren Code so umgestalten, dass diese Objekte in der Datenschicht gespeichert und abgerufen werden können. Beispiele finden Sie im Now in Android-Repository für Beispiele.

Beschränkungen

In diesem Abschnitt werden die aktuellen Einschränkungen für Navigation Compose beschrieben.

Schrittweise Migration zu Navigation Compose

Derzeit können Sie Navigation Compose nicht verwenden, wenn Sie in Ihrem Code noch Fragmente als Ziele verwenden. Wenn Sie Navigation Compose verwenden möchten, müssen alle Ihre Ziele Composables sein. Sie können diesen Feature Request in der Problemverfolgung verfolgen.

Übergangsanimationen

Ab Navigation 2.7.0-alpha01 wird das Festlegen benutzerdefinierter Übergänge, die zuvor von AnimatedNavHost unterstützt wurden, jetzt direkt in NavHost unterstützt. Weitere Informationen finden Sie in den Versionshinweisen.

Weitere Informationen

Weitere Informationen zur Migration zu Navigation Compose finden Sie in den folgenden Ressourcen:

  • Codelab zu Navigation Compose: In diesem Codelab lernen Sie die Grundlagen von Navigation Compose kennen.
  • Now in Android-Repository: Eine voll funktionsfähige Android-App die vollständig mit Kotlin und Jetpack Compose erstellt wurde. Sie folgt den Best Practices für Android-Design und ‑Entwicklung und enthält Navigation Compose.
  • Sunflower zu Jetpack Compose migrieren: Ein Blogpost, in dem die Migration der Sunflower-Beispiel-App von Ansichten zu Compose dokumentiert wird. Dazu gehört auch die Migration zu Navigation Compose.
  • Jetnews für jeden Bildschirm: Ein Blogpost, in dem die Refaktorierung und Migration der Jetnews-Beispiel-App dokumentiert wird, um alle Bildschirme mit Jetpack Compose und Navigation Compose zu unterstützen.