Jetpack Navigation zu Navigation Compose migrieren

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

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

Voraussetzungen für die Migration

Sie können zu Navigation Compose migrieren, sobald Sie alle Ihre Fragmente durch entsprechende Bildschirm-Composables ersetzen können. Bildschirm-Kompositionen können eine Mischung aus Compose- und View-Inhalten enthalten, aber alle Navigationsziele müssen Kompositionen sein, um die Migration zu Navigation Compose zu ermöglichen. Bis dahin sollten Sie weiterhin die fragmentbasierte Navigationskomponente in Ihrer interoperablen View- und Compose-Codebasis verwenden. Weitere Informationen finden Sie in der Dokumentation zur Navigationsinteroperabilität.

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 Composable-Container fungieren. In dieser Phase können Sie zu Navigation Compose migrieren.

Wenn Ihre App bereits einem UDF-Designmuster und unserem Leitfaden zur Architektur folgt, sollte die Migration zu Jetpack Compose und Navigation Compose keine größeren Refaktorierungen anderer Ebenen Ihrer App erfordern, 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 Compose-Element vom Typ App-level und fügen Sie es als Compose-Einstiegspunkt in Ihre Activity ein. 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 data object für Ziele, die keine Daten erfordern, und data class oder class für Ziele, die Daten erfordern.

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

  4. Richte den NavController an einem Ort ein, an dem alle komponierbaren Funktionen, die darauf verweisen müssen, Zugriff darauf haben. Das ist normalerweise in deiner komponierbaren Funktion App. Dieser Ansatz folgt den Prinzipien des Status-Hoisting und ermöglicht es Ihnen, den NavController als „Source of Truth“ für die Navigation zwischen zusammensetzbaren Bildschirmen und die Verwaltung des Back Stack zu verwenden:

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

  5. Erstellen Sie die NavHost Ihrer App im App-Composable und übergeben Sie die 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 die Anleitung zum Entwerfen Ihrer Compose-UI befolgt haben, insbesondere wie ViewModels und Navigationsereignisse an komponierbare Funktionen übergeben werden sollten, besteht der nächste Schritt darin, die Art und Weise zu ändern, wie Sie das ViewModel für jede Bildschirm-komponierbare Funktion bereitstellen. Sie können die Hilt-Injektion und den Integrationspunkt mit Compose und Navigation häufig ü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 composable Bildschirm, anstatt die gesamte navController zu übergeben. Dieser Ansatz folgt den Best Practices für das Bereitstellen von Ereignissen aus zusammensetzbaren Funktionen für Aufrufer und behält die navController als zentrale Informationsquelle bei.

    Daten können an ein Ziel übergeben werden, indem eine Instanz der für dieses Ziel definierten Routenklasse erstellt wird. Er kann 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-Navigationsabhä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 Mit Compose navigieren.

Komplexe Daten beim Navigieren abrufen

Wir raten dringend davon ab, komplexe Datenobjekte bei der Navigation zu übergeben. Übergebe stattdessen die minimal erforderlichen Informationen, z. B. eine eindeutige Kennung oder eine andere Form der ID, als Argumente, wenn du Navigationsaktionen ausführst. Komplexe Objekte sollten als Daten in einer zentralen Informationsquelle wie der Datenebene gespeichert werden. Weitere Informationen finden Sie unter Komplexe Daten beim Navigieren abrufen.

Wenn deine Fragmente komplexe Objekte als Argumente übergeben, solltest du deinen Code zuerst so refaktorieren, dass diese Objekte auf der Datenebene gespeichert und abgerufen werden können. Beispiele finden Sie im Now in Android-Repository.

Einschränkungen

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

Inkrementelle Migration zu Navigation Compose

Derzeit können Sie Navigation Compose nicht verwenden, wenn Sie in Ihrem Code noch Fragmente als Ziele verwenden. Damit Sie Navigation Compose verwenden können, müssen alle Ihre Ziele „Composables“ sein. Sie können diesen Feature Request im Issue Tracker verfolgen.

Übergangsanimationen

Ab Navigation 2.7.0-alpha01 wird die Unterstützung für das Festlegen benutzerdefinierter Übergänge, die zuvor von AnimatedNavHost stammten, 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 praxisorientierten 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.
  • Migrating Sunflower to Jetpack Compose: Ein Blogpost, in dem die Migration der Beispiel-App „Sunflower“ von Views zu Compose dokumentiert wird, einschließlich der Migration zu Navigation Compose.
  • Jetnews für alle Bildschirme: Ein Blogpost, in dem die Refaktorierung und Migration des Jetnews-Beispiels dokumentiert wird, um alle Bildschirme mit Jetpack Compose und Navigation Compose zu unterstützen.