Jetpack Navigation zu Navigation Compose migrieren

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

Auf dieser Seite wird beschrieben, wie Sie im Rahmen der Migration von View-basierter UI zu Jetpack Compose von einer Fragment-basierten Jetpack Navigation zu Navigation Compose migrieren.

Voraussetzungen für die Migration

Sie können zu Navigation Compose migrieren, sobald Sie alle Ihre Fragments durch entsprechende Screen-Composables ersetzen können. Screen-Composables können eine Mischung aus Compose- und View-Inhalten enthalten, aber alle Navigationsziele müssen Composables sein, um die Migration zu Navigation Compose zu ermöglichen. Bis dahin sollten Sie die Fragment-basierte Navigationskomponente weiterhin in Ihrem Interop-View- und Compose-Code verwenden. Weitere Informationen finden Sie in der Dokumentation zur Navigations-Interop-Funktion.

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 Fragments nur als Composable-Container fungieren. An diesem Punkt können Sie zu Navigation Compose migrieren.

Wenn Ihre App bereits einem UDF-Designmuster und unserem Architekturleitfaden folgt, sollten für die Migration zu Jetpack Compose und Navigation Compose keine größeren Refactorings 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 eine App-level komponierbare Funktion und fügen Sie sie Ihrem Activity als Compose-Einstiegspunkt hinzu. Ersetzen Sie dabei die Einrichtung des View-Layouts:

    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, 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 die NavController an einem Ort ein, an dem alle komponierbaren Funktionen, die darauf verweisen müssen, darauf zugreifen können. Das ist in der Regel in Ihrer komponierbaren Funktion App. Dieser Ansatz folgt den Prinzipien des State Hoisting und ermöglicht es Ihnen, NavController als Quelle der Wahrheit für die Navigation zwischen zusammensetzbaren Bildschirmen und die Verwaltung des Backstacks zu verwenden:

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

  5. Erstellen Sie die NavHost Ihrer App in der zusammensetzbaren Funktion App und übergeben Sie 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 das Navigationsdiagramm zu erstellen. Wenn jeder Bildschirm bereits zu Compose migriert wurde, besteht dieser Schritt nur darin, diese Bildschirm-Composables aus Ihren Fragments 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-Benutzeroberfläche 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 die ViewModel für jede Bildschirm-komponierbare Funktion bereitstellen. Sie können häufig die Hilt-Injection und den zugehörigen Integrationspunkt 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 zusammensetzbaren Bildschirm, anstatt das gesamte navController zu übergeben. Dieser Ansatz folgt den Best Practices für die Bereitstellung von Ereignissen aus zusammensetzbaren Funktionen für Aufrufer und behält 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 kann dann entweder direkt aus dem Backstack-Eintrag am Zielort 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 veraltete 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 dieselben Navigationsprinzipien.

Häufige Anwendungsfälle für die Migration:

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

Komplexe Daten während der Navigation abrufen

Wir raten dringend davon ab, beim Navigieren komplexe Datenobjekte zu übergeben. Übergeben Sie stattdessen die minimal erforderlichen Informationen, z. B. eine eindeutige Kennung oder eine andere Form von ID, als Argumente, wenn Sie Navigationsaktionen ausführen. Komplexe Objekte sollten als Daten in einer zentralen Datenquelle wie dem Data Layer gespeichert werden. Weitere Informationen finden Sie unter Komplexe Daten beim Navigieren abrufen.

Wenn Ihre Fragmente komplexe Objekte als Argumente übergeben, sollten Sie Ihren Code zuerst so umgestalten, dass diese Objekte in der Datenschicht 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 weiterhin Fragmente als Ziele in Ihrem Code 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 das Festlegen benutzerdefinierter Übergänge, das zuvor über AnimatedNavHost erfolgte, 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:

  • Navigation Compose-Codelab: 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, den Best Practices für Android-Design und ‑Entwicklung entspricht und Navigation Compose enthält.
  • Sunflower zu Jetpack Compose migrieren: In diesem Blogpost wird die Migration der Sunflower-Beispiel-App von Views zu Compose dokumentiert. Dazu gehört auch die Migration zu Navigation Compose.
  • Jetnews für jeden Bildschirm: 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.