Navigacija

U Android programiranju, moguće je napraviti da aplikacija ima nekoliko prozora. Recimo, moguće je da imamo jedan prozor koji zahteva od korisnika da se prijavi, pa onda drugi u kojem pristupa glavnom prozoru aplikacije, kao i nekoliko sporednih prozora za razne funkcionalnosti aplikacije. U ovom odeljku videćemo kako se može implementirati kretanje kroz prozore u Android Studio.
Pre nego što počnemo sa navigacijom, moramo dodati potrebne biblioteke, baš kao na sledećem linku.
Kao što smo definisali izgled našeg glavnog prozora (onog koji se otvara kada se aplikacija pokrene), tako možemo na isti način u zasebnim XML fajlovima u direktorijumu res/layouts definisati nove prozore aplikacije. Kada se to uradi, potrebno je definisati u zasebnom XML fajlu takozvani navigacioni graf. Navigacioni graf je usmereni graf koji čuva podatke o tome iz kog se prozora može preći u drugi prozor, pod kojim uslovima se prelazak odvija i ostale esencijalne informacije za menjanje prozora. Navigacioni graf se može dodati ručno, ali je ipak savet da se to uradi ugrađenim opcijama na sledeći način:

  1. U Android Studio, unutar fajl sistema kliknuti desnim klikom na direktorijum res i izabrati opciju New -> Android Resource File
  2. Nazvati fajl, recimo "nav_graph"
  3. U delu Resource type, izabrati opciju Navigation
Nakon što smo definisali navigacioni graf, otvoriće nam se Navigation Editor koji izgleda kao na slici ispod.

Navigation Editor

Navigation Editor se sastoji od tri sekcije, a to su redom (kao i na slici):

  1. Destination panel u kojem se nalaze informacije o grafu
  2. Graph Editor u kojem se nalazi vizuelna reprezentacija grafa
  3. Attributes u kojem se nalaze atributi trenutno izabranog objekta u navigacionom grafu

NavHostFragment

NavHostFragment je vrsta fragmenta zadužena za obavljanje operacije smenjivanja prozora unutar aplikacije. NavHostFragment se može, kao i svaki drugi fragment, definisati unutar XML fajla. Sve što je potrebno je dodati sledeće linije koda bilo gde (može čak i unutar nekog layout-a) u našem activity_main.xml:

<fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"

        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph" />

Kao i kod fragmenata, i ovde android:name ukazuje na ime klase koja se koristi. Atribut app:navGraph govori koji se navigacioni fajl koristi za upravljanje navigacijom. Atribut defaultNavHost je podešen na true, što znači da će sprečavati dejstvo sistemskog dugmeta Back. To praktično znači da dugme Back neće moći da nas vrati na prethodni prozor. Ukoliko postoji više NavHost-ova, onda samo jedan može imati atribut defaultNavHost podešen na true.
Moguće je NavHostFragment dodati i preko Layout Editor u aplikaciju. Potrebno je otvoriti activity_main.xml u Layout Editor i onda u odeljku Palette otvoriti Containers i u njemu pronaći NavHostFragment i dodati ga na aplikaciju. Kada se to uradi, otvoriće se prozor preko kojeg je moguće podesiti atribute baš kao što smo to uradili u XML fajlu.

Destinacije

Ključni deo navigacionog grafa su destinacije. Destinacija predstavljaju čvorove našeg navigacionog grafa. Destinacija se u navigacionom grafu pravu klikom na dugme New Destination koje izgleda kao na slici: New Destination Potom se mora izabrati opcija Create new destination ili se izabere neki od ranije definisanih fragmenata. U prozoru koji se pojavi, kreirati destinaciju. Atributi destinacije mogu da se menjaju u sekciji Attributes unutar Layout Editor. Ključna su sledeća četiri atributa:

Ako se neki od fragmenata ne vidi u Navigation Editor, potrebno je dodati mu atribut tools:layout. Recimo, ako se fragment zove mainPage, onda možemo podesiti tools:layout = "@layout/fragment_mainPage".

Povezivanje

Da bi se destinacije povezale, dovoljno je kliknuti na jednu od njih, pa potom na kružić koji se pojavi sa njene desne strane i prevučemo mišem do druge destinacije. Tada će se na grafu videti strelica. Na slici bi to izgledalo ovako:

Povezivanje

Strelice su takođe objekti našeg grafa, što znači da i one mogu imati atribute. Sada ćemo pokazati kako se može klikom na dugme prelaziti iz prozora u prozor. Recimo, imamo fragmente A i B i želimo iz A da pređemo u B klikom na dugme Jump. Tada bi naš kod izgleda otprilike ovako:

binding.jumpButton.setOnClickListener { view : View ->
    view.findNavController().navigate(R.id.action_A_to_B)
}

Odgovarajući delovi koda mogu biti drugačiji zbog ID-eva i naziva promenljivih. Strelica u kodu predstavlja definiciju lambda funkcije. Radi preglednijeg koda, korisno je koristiti lambda funkcije. U tom slučaju, nema potrebe čuvati funkciju na zasebnom mestu i onda je pozivati. Recimo, ekvivalent ovom kodu bi bio sledeći:

private fun jumpKlik(view: View) {
    view.findNavController().navigate(R.id.action_A_to_B)
}
...
binding.jumpButton.setOnClickListener {
    jumpKlik(it)
}

Vraćanje unazad

Kadgod promenimo destinaciju unutar naše aplikacije, sistem piše u back stek lokacije na kojima smo bili. Kada kliknemo dugme Back, onda se sa vrha steka skida destinacija na kojoj smo ranije bili i naša aplikacija nas preusmerava tu. U nekim situacijama, želeli bismo da kontrolišemo dejstvo dugmeta Back na našu aplikaciju.
Back stekom se može upravljati tako što će se podešavati takozvano pop ponašanje strelicima koje povezuju destinacije. Da bi se to uradilo, unutar Navigation Editor je potrebno izabrati strelicu kojoj želimo menjati ponašanje. U sekciji Attribute potrebno je locirati PopBehavior. Postoje dva parametra - popUpTo i popUpToInclusive. Ako je popUpTo uključen, a popUpToInclusive je false ili nije podešen, onda će se skidati sve destinacije sa steka dok se ne dođe do one koju smo postavili na popUpTo, ali ta destinacija ostaje na steku. Ako je popUpToInclusive postavljen na true, onda će se skinuti i ta destinacija na koju popUpTo pokazuje.

Razmena između fragmenata

U velikom broju situacija, može se desiti da fragmenti treba da komuniciraju jedni sa drugima. Recimo, u jednom fragmentu smo uneli neke informacije, a u drugom treba da ih pročitamo. Postoje razni načini da se omogući komunikacija među fragmentima. Jedan od njih je korišćenjem klase Bundle i funkcionalnosti biblioteke Safe Args. Bundle je klasa koja sadrži mapu imena promenljvih koje čuvamo i njihovih vrednosti. Fragment A će proslediti u Bundle objekat ono što treba da pošalje, a posle će fragment B to da pročita. Greške koje mogu da se jave su da tip podataka bude neispravan ili da ne postoji vrednost sa tim imenom ključa. Tada je ove greške potrebno uhvatiti, a za to nam služe funkcionalnosti biblioteke Safe Args.
Da bi se Safe Args koristio, mora se dodati najnovija biblioteka za to, kao u upustvu na sledećem linku. Kada to uradimo, potrebno je uključiti Safe Args plugin. To se radi tako što se u build.gradle fajlu pozicioniramo na deo kod plugina i dodamo liniju koda:

apply plugin: 'androidx.navigation.safeargs'

Kada se plugin uključi, jedna od stvari koje se dese je da se stvore klase za definisane fragmente. Recimo, ako smo imali klasu MojFragment, postojaće u sistemu i klasa MojFragmentDirections. Te klase ne možemo da vidimo, tj. njihova implementacija je sakrivena. One nam služe za prenos podataka preko fragmenata i njih ne možemo menjati iz razloga što se njihov sadržaj obnavlja prilikom svake kompilacije, pa naše promene ne mogu ostati sačuvane. Direction klase sadrže metode za kretanje između fragmenata. Recimo, ako imamo fragment AFragment i fragment BFragment i moguće je iz A doći u B, tada u klasi AFragmentDirections postoji metod actionAFragmentToBFragment(). Ovaj metod će nam služiti za komunikaciju između fragmenata. Dakle, sada je potrebno pozive metoda navigate izmeniti na sledeći način:

view.findNavController().navigate(AFragmentDirections.actionAFragmentToBFragment())

Ostaje sada da se implementira prenos podataka između fragmenta A i fragmenta B. Potrebno je otvoriti navigacioni fajl i onda izabrati fragment B (onaj u koji šaljemo informacije iz fragmenta A). U Attributes delu moramo pronaći deo Arguments. Tu kliknemo na znak +. Sada će se otvoriti prozor na kojem biramo ime argumenta i njegov tip. Primera radi, poslaćemo String message. Sada sve što je potrebno uraditi je u fragmentu A definisati parametar koji će da se šalje fragmentu B i proslediti ga pri navigaciji. Recimo, moguće je to uraditi ovako:

val message : String = "Hello from A"
view.findNavController().navigate(AFragmentDirections.actionAFragmentToBFragment(message))

Da bi se u fragmentu B pročitala poruka, moramo je otvoriti. To se radi korišćenjem klase Bundle na sledeći način.

val args = BFragmentArgs.fromBundle(requireArguments())
// u polju args.message se nalazi "Hello from A"