Jetpack Navigation zu Navigation Compose migrieren

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

Auf dieser Seite wird beschrieben, wie Sie im Rahmen der größeren Migration der ansichtenbasierten Benutzeroberfläche 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-Composeables ersetzt haben. Bildschirm-Composeables können eine Mischung aus Compose- und Ansichtsinhalten enthalten. Alle Navigationsziele müssen jedoch Composeables sein, damit die Migration von Navigations-Composes möglich ist. Bis dahin sollten Sie die fragmentbasierte Navigationskomponente in Ihrer Interoperabilitätsansicht und Compose-Codebasis weiterhin verwenden. Weitere Informationen finden Sie in der Dokumentation zur Navigationsinteroperabilität.

Die Verwendung von Navigation Compose in einer reinen Compose-App ist keine Voraussetzung. Sie können die fragmentbasierte Navigationskomponente weiterhin verwenden, solange Sie Fragmente zum Hosten Ihrer zusammensetzbaren Inhalte beibehalten.

Migrationsschritte

Unabhängig davon, ob Sie unserer empfohlenen Migrationsstrategie folgen oder einen anderen Ansatz verfolgen, werden Sie irgendwann an den Punkt kommen, an dem alle Navigationsziele Screen Composeables sind und Fragmente nur als Composeable-Container dienen. 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 abgesehen von der UI-Ebene keine größeren Refactorings anderer Schichten Ihrer App erfordern.

So migrieren Sie zu Navigation Compose:

  1. Fügen Sie Ihrer App die Navigation Compose-Abhängigkeit hinzu.
  2. Erstellen Sie ein App-level-Komposit und fügen Sie es Ihrem Activity als Einstiegspunkt für die Zusammenstellung hinzu. Ersetzen Sie damit 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, 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. Richte die NavController an einem Ort ein, an dem alle Composeables, die darauf verweisen müssen, darauf zugreifen können. Normalerweise befindet sie sich im App-Composeable. Dieser Ansatz folgt den Grundsätzen des Zustands-Hoisting und ermöglicht es, NavController als vertrauenswürdige Quelle für die Navigation zwischen zusammensetzbaren Bildschirmen und die Aufrechterhaltung des Backstacks zu verwenden:

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

  5. Erstellen Sie die NavHost Ihrer App im App-Komposit 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 Ihren Navigationsgraphen zu erstellen. Wenn jeder Bildschirm bereits zu Compose migriert wurde, besteht dieser Schritt nur aus dem Extrahieren dieser Bildschirm-Composeables aus Ihren Fragmenten in die composable-Ziele:

    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 zum Entwerfen der Compose-Benutzeroberfläche gefolgt sind, insbesondere dazu, wie ViewModels und Navigationsereignisse an Composables übergeben werden sollten, müssen Sie im nächsten Schritt ändern, wie Sie die ViewModel für jedes Bildschirm-Composeable bereitstellen. Sie können die Hilt-Injection und den Integrationspunkt mit Compose und Navigation über hiltViewModel häufig verwenden:

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

  8. Ersetzen Sie alle findNavController()-Navigationsanrufe durch navController-Anrufe und übergeben Sie diese als Navigationsereignisse an jeden zusammensetzbaren Bildschirm, anstatt die gesamte navController zu übergeben. Dieser Ansatz folgt den Best Practices, Ereignisse aus kombinierbaren Funktionen für Aufrufer freizugeben, und behält die 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 Ziel oder über ein 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ötige Navigations- und andere 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.

Gängige Anwendungsfälle für die Migration:

Weitere Informationen zu diesen Anwendungsfällen finden Sie unter Mit Compose navigieren.

Komplexe Daten bei der Navigation abrufen

Wir empfehlen dringend, bei der Navigation keine komplexen Datenobjekte zu übergeben. Geben Sie stattdessen die minimal erforderlichen Informationen, z. B. eine eindeutige Kennung oder eine andere Art von ID, als Argumente an, wenn Sie Navigationsaktionen ausführen. Komplexe Objekte sollten als Daten in einer einzigen zentralen Informationsquelle gespeichert werden, z. B. in der Datenebene. Weitere Informationen finden Sie unter Abrufen komplexer Daten beim Navigieren.

Wenn Ihre Fragmente komplexe Objekte als Argumente übergeben, sollten Sie Ihren Code zuerst so umstrukturieren, dass diese Objekte in der Datenebene gespeichert und abgerufen werden können. Beispiele finden Sie im Repository „Jetzt in Android“.

Beschrä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 weiterhin Fragmente als Ziele verwenden. Damit Sie Navigation Compose verwenden können, müssen alle Ziele Composables sein. Sie können den Status dieser Funktionsanfrage im Issue Tracker verfolgen.

Übergangsanimationen

Ab Navigation 2.7.0-alpha01 wird die Einstellung benutzerdefinierter Übergänge, die bisher über AnimatedNavHost erfolgte, jetzt direkt über 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.
  • Jetzt im Android-Repository: Eine voll funktionsfähige Android-App, die vollständig mit Kotlin und Jetpack Compose entwickelt wurde, die den Best Practices für Android-Design und -Entwicklung folgt und Navigation Compose enthält.
  • Sunflower zu Jetpack Compose migrieren: In diesem Blogpost wird die Migration der Beispiel-App „Sunflower“ von Views zu Compose dokumentiert, einschließlich der Migration zu Navigation Compose.
  • Jetnews für alle Bildschirme: In diesem Blogpost wird die Umstrukturierung und Migration des Jetnews-Beispiels beschrieben, um alle Bildschirme mit Jetpack Compose und Navigation Compose zu unterstützen.