Sii Polska

SII UKRAINE

SII SWEDEN

  • Szkolenia
  • Kariera
Dołącz do nas Kontakt
Wstecz

Sii Polska

SII UKRAINE

SII SWEDEN

Wstecz
Jetpack Compose Navigation w pigułce

Jetpack Compose jest nowoczesną biblioteką do tworzenia natywnych aplikacji na platformę Android. W znacznym stopniu usprawnia ona proces tworzenia interfejsów użytkownika (UI), umożliwiając przygotowywanie ekranów i komponentów przy wykorzystaniu deklaratywnego kodu napisanego w języku Kotlin. Compose oferuje również własny zestaw rozwiązań do zarządzania nawigacją w aplikacji.

W artykule przyjrzymy się temu, jak funkcjonuje nawigacja w Jetpack Compose, jak ją poprawnie zaimplementować oraz w jaki sposób przekazywać złożone struktury pomiędzy ekranami.

Implementacja w projekcie

Aby zaimplementować w projekcie bibliotekę navigation-compose, musimy dodać odpowiednią zależność do pliku build.gradle:

Dodawanie zależności do pliku build.gradle
Ryc. 1 Dodawanie zależności do pliku build.gradle (Źródło: developer.android.com)

Dokładny sposób implementacji może się różnić zależnie od zastosowanego sposobu konfiguracji projektu.

Komponenty nawigacyjne Jetpack Compose

Do zarządzania nawigacją przy z użyciem biblioteki navigation-compose wykorzystuje się następujące komponenty:

KomponentCharakterystyka
NavHostKomponent definiujący miejsce, w którym odbywa się nawigacja. Wyświetla aktualną destynację aplikacji.
NavControllerCentralny komponent zarządzający nawigacją pomiędzy destynacjami. Dysponuje metodami pozwalający m.in na nawigowanie, obsługę deep linków i cofanie po destynacjach.
NavGraphStruktura danych definiująca wszystkie destynacje w aplikacji oraz określająca jak są ze sobą powiązane.
DestinationWęzeł w grafie nawigacyjnym, określające „miejsce” w aplikacji, do którego chcemy nawigować. W większości przypadków będzie to ekran composable’owy, chociaż biblioteka umożliwia również stosowanie jako destynacji dialogów oraz zagnieżdżonych grafów nawigacyjnych.
RouteKomponent, który w sposób unikatowy definiuje destynację (cel) nawigacji oraz wszelkie dane przez nią wymagane.
Tab. 1 Komponenty do zarządzania nawigacją

Poniżej przedstawiono przykład podstawowej struktury nawigacji:

Przykład podstawowej struktury nawigacji
Ryc. 2 Przykład podstawowej struktury nawigacji

W powyższym przykładzie:

  • Utworzony obiekt navController zarządza nawigacją.
  • NavHost inicjuje strukturę nawigacji. Przekazane mu argumenty to utworzony wcześniej navController oraz startDestination, wskazujące na ekran początkowy aplikacji (w tym przypadku screenA).
  • W ciele NavHosta zdefiniowano NavigationGraph, wskazując poszczególne destynacje aplikacji. W tym przypadku występują dwie destynacje composable: screenA oraz screenB.
  • Destynacje composable zawierają w sobie określone ekrany w formie funkcji composable.

Przekazywanie danych w trakcie nawigacji – ścieżki z argumentami

Częstym scenariuszem w przypadku przygotowywania aplikacji mobilnej jest konieczność przekazywania danych pomiędzy różnymi ekranami. Jednym ze sposobów realizacji tego zadania przy użyciu Jetpack Compose Navigation jest skorzystanie wykorzystanie ścieżek z argumentami.

W celu przedstawienia tego konceptu zaplanowano scenariusz, w którym z destynacji screenA należy przekazać do destynacji screenB takie dane jak userId oraz userName.

Poniżej przedstawiono przykład implementacyjny, będący rozszerzeniem wcześniej przygotowanego kodu:

Przykład implementacyjny – rozszerzenie
Ryc. 3 Przykład implementacyjny – rozszerzenie

W powyższym przykładzie:

  • route destynacji screenB został rozszerzony o 2 argumenty: userId oraz userName.
  • Argumenty userId oraz userName zostały wylistowane w polu arguments. Poza określeniem nazw poszczególnych argumentów wskazywany jest również ich typ – odpowiednio NavType.IntType dla userId oraz NavType.StringType dla userName.
  • W ciele destynacji composable następuje próba pobrania określonych wcześniej argumentów z NavBackStackEntry poprzez podanie ich kluczy (userId oraz userName).
  • Pobrane dane są następnie przekazywane do ekranu ScreenB.

Wywołanie funkcji do nawigacji na navControllerze przy wykorzystaniu ścieżek z argumentami jest widoczne na poniższym przykładzie prezentującym @Composable ScreenA:

Wywołanie funkcji do nawigacji – @Composable ScreenA
Ryc. 4 Wywołanie funkcji do nawigacji – @Composable ScreenA

W powyższym przykładzie:

  • Zadeklarowano argumenty userId oraz userName wartościami 123 oraz Jacek.
  • Nawigacja do destynacji screenB następuje poprzez wywołanie całej ścieżki z argumentami: „screenB/${userId}/${userName}”.

Nawigacja przy wykorzystaniu ścieżek z argumentami jest prosta w implementacji i łatwo rozszerzalna. Potencjalne problemy mogą być spowodowane tym, iż ten sposób nie zapewnia type safety. Typ każdegoz argumentów jest ustalany ręcznie przez dewelopera, a ich poprawność nie jest sprawdzana w trakcie kompilacji aplikacji. Występowanie błędów podczas runtime’u wydłuża proces developmentu. Dodatkowo podejście ścieżek z argumentami wymusza pisanie dużych ilości kodu boilerplate w celu określenia dokładnych adresów destynacji oraz typów poszczególnych argumentów.

W przygotowanym przykładzie do adresów wykorzystano route’y w postaci czystych obiektów String. W zastosowaniach komercyjnych korzysta się z tego sposobu, zabezpieczając stringowe route’y np. poprzez utworzenie sealed class i zapis adresów w ich polach.

Przekazywanie danych w trakcie nawigacji – obiekty typesafe @Serializable

Google w swojej oficjalnej dokumentacji proponuje korzystanie z innej metody nawigacji w nowych projektach. Polega ona na wykorzystaniu obiektów i/lub klas oznaczonych jako @Serializable, które zastępują omówione wcześniej stringowe route’y.

Aby móc skorzystać z tagowania @Serializable do nawigacji, konieczne jest dodanie odpowiedniej zależności do pliku build.gradle:

image5 - Jetpack Compose Navigation w pigułce
Dodanie zależności do pliku build.gradle (Źródło: proandroiddev.com)
Ryc. 5 Dodanie zależności do pliku build.gradle (Źródło: proandroiddev.com)

Po synchronizacji możliwe będzie skorzystanie z tagów @Serializable w celu przygotowania unikalnych obiektów i klas, które posłużą w celach nawigacyjnych.

Przykład implementacyjny przygotowałem poniżej.

Przykład implementacyjny – @Serializable
Ryc. 6 Przykład implementacyjny – @Serializable

W powyższym przykładzie:

  • Zadeklarowano object ScreenA pełniący funkcję identyfikatora ekranu @Composable ScreenA. Nawigacja do ekranu ScreenA nie wymaga przekazania argumentów, dlatego wykorzystanie struktury object jest wystarczające.
  • Zadeklarowano data class ScreenB będący identyfikatorem ekranu @Composable ScreenB. Jak we wcześniej omówionym przykładzie, ScreenB oczekuje przekazania mu dwóch argumentów: userId oraz userName. Z tego powodu indentyfikatorem jest data class z dwoma polami. Pole userName jest opcjonalne, dlatego zostało oznaczone jako nullable z domyślną wartością.

Po przygotowaniu identyfikatorów ekranów w postaci klas i obiektów @Serializable możliwe jest utworzenie nowego grafu nawigacyjnego. Wykorzystanie tego sposobu znacznie upraszcza strukturę grafu, co jest widoczne na załączonym przykładzie:

Utworzenie nowego grafu nawigacyjnego
Ryc. 7 Utworzenie nowego grafu nawigacyjnego

W powyższym przykładzie:

  • navController został utworzony w taki sam sposób, jak przy nawigacji z route’ami.
  • Jako startDestination wskazano @Serializable object ScreenA.
  • Destynacje composable mają bezpośrednio przekazany identyfikator ScreenA / ScreenB.
  • Dostęp do przekazanych argumentów odbywa się poprzez wywołanie metody toRoute<>() z określeniem typu (w tym przypadku ScreenB).

Wywołanie funkcji do nawigacji na navControllerze przy wykorzystaniu ścieżek z argumentami jest widoczny na poniższym przykładzie prezentującym odpowiednio zmodyfikowany @Composable ScreenA:

Wywołanie funkcji do nawigacji na navControllerze
Ryc. 8 Wywołanie funkcji do nawigacji na navControllerze

W powyższym przykładzie:

  • Nawigacja do ekranu ScreenB odbywa się poprzez wywołanie metody navigate() na navController, z przekazaniem nowo utworzonego obiektu klasy ScreenB z odpowiednio określonymi parametrami userId oraz userName.

Korzyści z wykorzystania obiektów i klas otagowanych jako @Serializable

Nawigacja przy wykorzystaniu obiektów i klas otagowanych jako @Serializable niesie za sobą wiele korzyści.

Przede wszystkim jest to rozwiązanie type safe. Deweloper nie musi się obawiać popełnienia pomyłki przy określaniu typu argumentu w sytuacji, jeżeli jest niezbędne przekazanie danych do kolejnego ekranu. Argumenty, jako pola data class, mają z góry określony typ, zatem w przypadku pomyłki niemożliwe będzie skompilowanie aplikacji. Dodatkowo, opisane rozwiązanie jest zdecydowanie bardziej czytelne dzięki braku konieczności pisania kodu boilerplate określającego typ i nazwę każdego z argumentów nawigacyjnych.

Type safe navigation jest domyślnym rozwiązaniem wskazywanym w dokumentacji Android do nawigacji pomiędzy ekranami jak i do przekazywania danych pomiędzy nimi. Należy jednocześnie pamiętać o tym, że nie zaleca się przekazywania skomplikowanych struktur w trakcie nawigacji. Dobrą praktyką jest przekazywanie maksymalnie prostych informacji, jak np. klucz ID, a odczyt pozostałych danych powinien nastąpić z oddzielnego źródła, np. repozytorium. Dzięki pojedynczemu źródłu prawdy będziemy mieli pewność, że wymagane dane w naszej destynacji są aktualne.

Nawigacja wstecz

W Jetpack Compose nawigacja wstecz jest obsługiwana za pomocą funkcji popBackStack(), wywoływanej
na navControllerze. Umożliwia ona cofnięcie się do poprzedniego ekranu poprzez usunięcie aktualnego ekranu ze stosu – w efekcie następuje powrót do poprzedniego widoku.

W celu wykonania nawigacji wstecz do konkretnego ekranu znajdującego się na stosie nawigacyjnym możliwe jest wywołanie jednej z metod będącej przeciążeniem popBackStack(). Identyfikator docelowej destynacji można przekazać w formie route będącej obiektem typu String lub przy wykorzystaniu obiektów i klas @Serialized, co zaprezentowano na poniższym przykładzie:

Wywołanie jednej z metod będącej przeciążeniem popBackStack()
Ryc. 9 Wywołanie jednej z metod będącej przeciążeniem popBackStack()

W powyższym przykładzie:

  • Na navController następuje wywołanie przeciążonej metody popBackStack() z przekazaniem typu naszej destynacji @Serialized ScreenA.
  • Parametr inclusive w metodzie popBackStack() określa, czy ze stosu nawigacyjnego ma również zostać usunięta destynacja, do której następuje nawigacja wstecz.

Nawigacja wstecz z przekazywaniem argumentu

Biblioteka nawigacyjna zapewnia narzędzia, które umożliwiają nawigowanie wstecz do poprzednich ekranów z przekazaniem informacji zwrotnej. Przesłanie informacji obywa się poprzez aktualizację previousBackStackEntry i savedStateHandle oraz odczyt przekazywanych danych na poziomie grafu nawigacyjnego.

Poniżej znajdziecie wcześniejszy przykład zmodyfikowany o możliwość przesłania informacji podczas nawigacji wstecz:

Przesyłania informacji podczas nawigacji wstecz
Ryc. 10 Przesyłania informacji podczas nawigacji wstecz

W powyższym przykładzie:

  • navController umożliwia dostęp do previousBackStackEntry, skąd możliwe jest wywołanie savedStateHandle.
  • W savedStateHandle możliwe jest ustawienie argumentu w postaci klucz-wartość (key-value), w tym przypadku kluczem jest numer, a wartość to 456.
  • Następnie następuje faktyczna nawigacja poprzez wywołanie metody popBackStack() na navControllerze.

W celu pozyskania danych przekazywanych wstecz konieczna była aktualizacja grafu nawigacyjnego, co przedstawiono poniżej:

Aktualizacja grafu nawigacyjnego
Ryc. 11 Aktualizacja grafu nawigacyjnego

W powyższym przykładzie:

  • W composable<ScreenA> poprzez odwołanie do savedStateHandle i odniesienie się do klucza number tworzony jest obiekt number – mający wartość przekazaną z ekranu ScreenB (456).
  • Pozyskana informacja w postaci obiektu number jest przekazywana do ekranu ScreenA.

Podsumowanie

Nawigowanie między ekranami to nieodłączny element niemal każdej aplikacji mobilnej. Poznanie i zrozumienie narzędzi, które to umożliwiają, jest kluczowe do tworzenia nowoczesnych aplikacji na platformę Android szybko i efektywnie.

Nawigacja w Jetpack Compose jak i w samym systemie Android to bardzo obszerne zagadnienia, których sam opis techniczny w dokumentacji jest zawarty w kilkudziesięciu stronach. W artykule przedstawiłem jedynie podstawowe koncepty i przykłady dotyczące implementacji nawigacji w Jetpack Compose. Stanowią one jednak solidną podstawę do dalszego poznawania technologii, próbowania rozwiązań i wykorzystywania jej w praktyce.

***

Jeśli interesuje Cię obszar mobile, zajrzyj również do innych artykułów naszych specjalistów.

5/5
Ocena
5/5
Avatar

O autorze

Jacek Kamiński

Senior Software Engineer z ponad 6-letnim doświadczeniem komercyjnym. W przeszłości pracował z różnymi technologiami, obecnie skupia się na developmencie mobilnym, ze wskazaniem na system Android. W swojej pracy ceni sobie możliwość rozwoju, podejmowania ciekawych wyzwań oraz współpracy z innymi osobami. W wolnym czasie lubi majsterkować, grać na pianinie, gotować i spędzać czas ze swoim czworonogiem

Wszystkie artykuły autora

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Może Cię również zainteresować

Dołącz do nas

Sprawdź oferty pracy

Pokaż wyniki
Dołącz do nas Kontakt

This content is available only in one language version.
You will be redirected to home page.

Are you sure you want to leave this page?