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 Fragmente durch entsprechende zusammensetzbare Bildschirmelemente ersetzen konnten. 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 eine zusammensetzbare Funktion App-level und fügen Sie sie dem Activity als Einstiegspunkt zum Schreiben hinzu. Dadurch wird die Einrichtung des Layouts „View“ ersetzt:

    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. Platzieren Sie NavController an einem Ort, an dem alle zusammensetzbaren Funktionen, die darauf verweisen müssen, Zugriff darauf haben. Das befindet sich normalerweise innerhalb der zusammensetzbaren Datei App. 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. Erstelle das NavHost deiner App in der zusammensetzbaren App-Funktion und übergib das 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 zuvor zu „Schreiben“ migriert wurde, besteht dieser Schritt nur darin, diese zusammensetzbaren Funktionen 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 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()-Navigationsaufrufe durch die navController und übergeben Sie diese als Navigationsereignisse an jeden zusammensetzbaren Bildschirm, anstatt das 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, relevante XML-Layouts, unnötige Navigationselemente 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 bei 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 die Funktionsanfrage in der Problemverfolgung einsehen.

Übergangsanimationen

Ab Navigation 2.7.0-alpha01 wird die Einstellung benutzerdefinierter Übergänge, die bisher ü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:

  • 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 for eachscreen: Ein Blogpost, der die Refaktorierung und Migration des Jetnews-Beispiels dokumentiert, um alle Bildschirme mit Jetpack Compose und Navigation Compose zu unterstützen.