JetPack Navigation Component

Sami Deliceli
Mobillium
Published in
9 min readJul 22, 2020

Geliştirme yaparken en çok zorlandığımız konuların başında, bir ekrandan diğer ekrana geçmeyi sağlayacak yapıyı oluşturmak ve bu yapıyı sağlıklı bir şekilde yönetmek gelir. Navigation Component da tam olarak bu probleme çözüm üretmek ve yazılımcı üzerindeki yükü hafifletip yazılımcının önem verdiği koda daha çok odaklanmasını sağlamak amacıyla geliştirilmiş bir kütüphanedir.

Navigation Component detaylarına geçmeden önce bu teknolojinin temelinde yatan “Single Activity” mimarisinden bahsetmek istiyorum.

Single Activity Mimarisi

Bu mimaride hedeflenen uygulamanın bir activity ve birden fazla fragment’tan oluşmasıdır. Uygulamadaki ekranlarımızı fragment’lar ile sunacağız ve oluşturduğumuz activity içerisinde de bu fragment’lar arasındaki ekran geçişlerini yöneteceğiz. Daha önceleri, anlayış bir activity bir işi yapmalı şeklindeyken zaman içinde ortaya çıkan birçok problemlerden dolayı Single Activity mimarisine geçiş başlamıştır.

Karşılaşılan problemler aşağıdaki şekilde;

  • Activity’lerin oluşturulma sürelerinin fazla olması
  • Activity’de framework’ün geriye dönük destek problemi
  • Animasyonlarda ve Geçişlerdeki (transition) tutarsızlıklar
  • Bellek problemleri

Single Activity mimarisine geçişi, yukarıdaki problemlerin çözümü yanında, aşağıdaki maddelerde hızlandırmıştır.

  • Tek bir yerde Toolbar yönetimi
  • Kolay veri taşınması
  • Sayfa yönlendirmelerinin kolaylığı
  • Google’in önerdiği mimari olması
  • BottomNavigation ve NavigationDrawer yapıları

Navigation Component

Single Activity mimarisiyle birlikte yukarıda bahsedilen problemler çözülmeye başlansa da bu sefer de fragment stack yönetimi, kullanıcının geri tuşuna bastığında doğru ekrana gitmesi gibi problemler ortaya çıktı. Tam bu noktada yazılımcının üzerindeki yükü minimuma indirmek amacıyla Jetpack Navigation Component Android Studio 3.3 sürümü ile hayatımıza girdi. Temel olarak faydalarını saymak gerekirse;

  • Fragment’lar arasında yönlendirmelerin kolaylığı
  • Android studio üzerinde sunulan editör ile uygulama akışının görselleştirilmesi
  • Back (Phone Back) ve Up (Toolbar Back) butonları ile doğru ekrana gitmesinin organize edilmesi
  • Animasyon ve Geçişler
  • Deeplink yönetimi
  • BottomNavigationBar ve NavigationDrawer için sunulan destek ile birkaç satır kod ile yapılandırma
  • Safe Args adlı gradle eklentisi ile veri alışverişinde tip güvenliğinin sağlanması
  • ViewModel desteği — UI ile ilgili verileri grafiğin fragment’lar arasında paylaşmak için ViewModel’i kullanabilmesi

Prensipler

Navigation Component kullansak da kullanmasak da uyulması önerilen prensiplerden bahsetmek istiyorum.

Sabit Başlangıç Ekranı: Uygulama için bir başlangıç ekranı belirlenmeli. Kullanıcının uygulamada geri tuşuyla gelebileceği son ekran da bu ekran olmalı.

Navigation Stack Yapısı: Uygulamamızdan kullanıcının gezdiği ekranlar bir stack yapısında tutulmalı. Her gidilen yeni ekran stack’in üstüne gelecek şekilde doldurulmalı. Kullanıcı geri gittikçe de stack’in en üst ekranı stack’ten silinmelidir.

Up ve Back butonları işlevi tutarlı olmalıdır. Telefonda geri gitmek için kullanılan back butonu ile toolbarda geri gelmek için kullanılan up butonu aynı işlevleri yerine getirmelidir.

Up ve Back butonları uygulamadan çıkmamalıdır. Up ve Back butonları kullanıcıya hala geri gidebileceği bir ekran olduğunu bildirdikleri için uygulamadan çıkmamalıdır. Yani stack’teki son sayfada toolbar da up butonu olmamalıdır.

DeepLink ile açılan bir ekran doğal olarak açılmış yapıda olmalıdır. Kullanıcının bir linke tıklayarak deeplink ile bir ekrana gittiğin düşünelim. Oluşan stack, kullanıcının manuel olarak o ekrana gitmesi ile oluşacak stack yapısı ile aynı olmalıdır. Yani DeepLink ile yönlendirme, manuel olarak ekran açılmasını simule etmeli.

Kurulum

Yukarıdaki kodu gradle dosyamıza ekledikten sonra kurulum işlemimiz tamamlanmış oluyor.

Navigation Component temel yapıları olarak: NavGraph, NavHostFragment, Action, Destination’ı sayabiliriz. Sırasıyla nedir, nasıl projemizde kullanırız şeklinde bu yapılara değineceğim.

NavGraph nedir, projeye nasıl eklenir?

NavGraph projemizde ekranlar arasındaki geçişleri, animasyonları ve birçok özelliği ayarlamak için kullanacağımız bir yapı. Programatik ve xml içerisinden oluşturabiliriz. res -> navigation klasörü altına bir xml dosyası oluşturacağız. Bu dosya da yukarıdaki ayarlamaları yapacağımız bir editör sunacak bizim için.

NavGraph Ekleme:

  • Projemizde res klasörü sağ tık -> New -> Android Resource File
  • Graph ismini giriyoruz (auth_graph, profile_graph)
  • Resource Type olarak Navigation seçip OK diyoruz.

Android Studi eğer res klasörü altına navigation klasörü varsa dosyayı içerisine oluşturacak yoksa da klasörü kendi oluşturacak. Oluşturduğumuz xml dosyasını açtığımızda aşağıdaki şekilde bir ekran açılacak.

Bu ekrandaki bölümler;

  1. Destinations panel: Graph’ımıza eklediğimiz ekranları listeler
  2. Graph Editor: Ekranlar arası kurduğumuz ilişkilerin kurulmasını ve görselleştirmesini sağlar.
  3. Attributes: Seçilen ekran veya ilişkiye ait özellikleri gösterir.

Action & Destination Nedir?

Destination: Navigation editörün eklediğimiz ekranlara destination olarak tanımlanır. Yukarıdaki görseli incelediğimizde 1 numara ile gösterilen ekran bir destination’ı belirtir. Tabi homeFragment ve notifications ekranları da bir destination’ı belirtir.

Action: Destinationlar arasında mantıksal olarak kurduğumuz bağlar action olarak tanımlanır. Görselde 2 ile gösterilen ok, dashboardFragment ve notificationsFragment arasındaki action’ı belirtir. Bu action dashboard’a notificationa navigate(yönlendirme) yapılabileceğini belirtir.

NavHostFragment ve NavController

Graphımızı oluşturduktan sonra, activity ile graphı ilişkilendireceğimiz ve destination değişimlerinin gerçekleşeceği bir container’ı activity xml’inde tanımlamamız gerekecek. Container olarak NavHostFragment kullanacağız. NavHostFragment destination’lar arasında yönlendirmeyi sağlayacak graph ile ilişkendirilmiş NavController’ı içerir. İleride örnek kullanımına yer vereceğim.

İster manuel olarak istersek de layout editorde palette -> Containers -> NavHostFragment şeklinde ekleyebiliriz. Activity xml’inde aşağıdaki şekilde bir kod bloğu oluşacak

android:name => NavHost implementation sınıfını belirtir.

app:defaultNavHost => Bu attribute de default nav hostu belirtir. Çoklu ekran desteği gibi durumlarda birden fazla NavHost olabilir. Bir NavHost default olarak belirtilmelidir

app:navGraph => Nav Hostun hangi graph ile ilişkilendirileceğini belirtir.

Destination Ekleme

Navigation Editor’deki add butonuna tıklayarak destination ekleyebiliriz.

Tıkladığımızda ekleyebileceğimiz destination’lar ve placeholder’lar listelenir. Buradan seçimimizi yaptığımızda da graph editör kısmına preview ile birlikte destination eklenir.

Placeholder’ları akış için gerekli ama daha oluşturulmamış destinationları görselleştirmek için kullanabiliriz. Ama bu şekilde uygulama çalıştırıldığında runtime error alınır.

Bir destination ekledikten sonra oluşan xml kodu inceleyelim.

app:startDestination => Her graph bir başlangıç destination’ına ihtiyaç sunar. NavHost başlangıçta bu destination’ı gösterir. startDestination olarak belirlenmiş destination’ın editörde ismi yanında ev iconu bulunur.

<fragment => Bu blok eklediğimiz destination’ı tanımlar. Eklediğimiz her destination bir fragment tagli kod bloğu oluşturacak.

Action Ekleme

Destination’ları ekledikten sonra mantıksal ilişkileri oluşturalım. Editör alanında bir destination üzerine geldiğimizde, sağında yuvarlak bir alan belirir. Bu alandan tutup diğer destination’ın üzerine bıraktığımızda 2 destination arasında bir action oluşmuş olur. Action oluştuğunda bir action kod bloğu generate edilir. Activity tarafında navigate işlemini bu generate edilen kodu kullanarak gerçekleştireceğiz. Action ekleme işlemini xml tarafında da yapabiliriz. Action eklediğimizde xml kısmında aşağıdaki şekilde bir kod oluşur.

Action kısmına baktığımızda kodda id ve destination attribute’leri oluşturuldu.

id => Kod kısmında yönlendirme yaparken action’a bu id üzerinden ulaşacağız.

destination => Bu action ile yönlendirme yapılacak fragment id’sini belirtir. Bu id nav_garph xml içerisindeki başka bir fragment’ın id’sidir. Örneğe baktığımızda bir alttaki homeFragment’ı gösterdiğini görürüz.

NavController’a erişme

Şu ana kadar öğrendiklerimizi birleştirip ekran değişiminin nasıl yapılacağı konusuna geçelim. Ekran değişimini gerçekleştirmek için NavController, NavHost ile ilişki kurup NavGraph’a ulaşacak. Böylelikle NavGraph içerisindeki action’lara ulaşmış olacağız. Daha sonra bu actionları kullanarak kolayca ekranlar arasında gezinti yapacağız. NavController’a nasıl ulaşacağımıza bakalım.

Java
Kotlin

viewId ile belirtilen parametreler NavHostFragment id’sini belirtiyor. Burada örnekleri incelersek activity’de viewId gönderirken fragment’ta göndermiyoruz. Bunun nedeni fragment’ın parent activity’si üzerinden NavHostFragment’a ulaşabiliyor olmasıdır.

Destination’a yönlendirme

NavHost’a ulaştıktan sonra bulunduğumuz destination’a sahip actionlara ekranımızı nagivate edebiliriz.

Fragment kısmını incelediğimizde, öncelikle NavController’ı buluyoruz. Daha sonra navigate fonksiyonuna gitmek istediğimiz ekrana ait action’ı parametre olarak gönderip ekran değişimini sağlıyoruz. Burada dikkat etmemiz gereken nokta; yazdığımız action bulunduğumuz ekran için tanımlanmış bir action olmalıdır. Aksi halde runtime hata alarak uygulama durdurulacak.

Animasyonlar

Ekranlar arası geçişi daha etkili hale getirmek için animasyonlar kullanabiliriz. Navigation Component yazılımcının çok rahat ettirdiği konulardan biri diyebiliriz. Bir örnek göstermemiz gerekirse;

Yukarıdaki action dashboard’dan home ekranına giden bir ilişkiyi ifade ediyor.

Kullanıcı Dashboard ekranından Home ekranına geçtiğinde; Dashbord’da exitAnim, Home ekranında enterAnim etkisini gösteririr.

Kullanıcı Home ekranından back tuşu ile geri döndüğünü varsayalım. Home ekranında popExitAnim, Dashboard ekranında popEnter anim etkisini gösterir.

Transitions (Geçişler)

Transitiondan bahsetmek gerekirse, içerisinde image de olan bir recyclerview elemanımız olduğunu varsayalım. Bu elemana tıkladığımızda açılacak detay ekranındaki bir alana bu image’in animasyonlu bir şekilde yerleşmesini istiyorsak burada transition kullanabiliriz. Bu işlemin Navigation Component ile nasıl yapıldığına bakalım.

Fragment’a navigate ederken
Activity’ye navigate ederken

Yukarıdaki kodlarda, transition kullanmak istediğimizde, destination’ımız fragment olurken nasıl, activity olurken nasıl navigate etmemiz gerektiğini gösteriyor. options’da transition uygulayacağımız view’ları belirtip, navigate fonksiyonuna parametre olarak geçip kolaylıkla transition’ı uyguluyoruz.

import android.util.Pair as UtilPair => Bu kısım tamamen sınıf çakışmalarını önlemek amaçlı yazıldı.

Circular Logic Nedir ?

Yukarıdaki gibi bir graphımız olduğunuz düşünelim. Kullanıcı a’dan b’ye, b’den c’ye ve c’den de tekrar a’ya sonra tekrar a’dan b’ye şeklinde sürekli gidebiliyor. Aslında bu da uygulamayı bir döngüye sokuyor. Buna da circular logic diyoruz. Bu durumda sağlıksız bir stack yapısına sahip oluyoruz.

popUpTo ve popUpToInclusive

Circular Logic ile birlikte navigation stack’imizin şişmesinden kaynaklanan problemleri çözmek adına yukarıdaki attribute’leri kullanabiliriz.

Senaryomuzda kullanıcının sırasıyla şu sınıfları gezdiğini kabul edelim.

a -> b -> c-> a -> b -> c -> a bu şekilde devam edebilir. Bu anlamsız crashlere ve kulanıcının uygulamadan çıkmak için defalarca geri yapmasına yol açar.

c -> a arasındaki action’ı aşağıdaki şekilde düzenlediğimizde neler olduğundan bahsedelim.

Yukarıdaki action’ı tanımladığımızda kullanıcı a -> b -> c şeklinde c sayfasına gitti diyelim. c den a’ya gitmek istediğinde;

app:popUpTo=”@+id/a” attribute’u ile b ve c stackten silinir. bu attribute a’ya kadar stack’i temizler. Ama bu durumda stack’te 2 tane a olur. Biri yeni navigate ettiğimiz a diğeri başlangıçtaki a destination’ı.

Eğer başlangıçtaki a destination’ı da stackten silinsin dersek de popUpToInclusive attribute’ünü true olarak ekliyoruz. Böylelikle stack’te sadece a kalıyor.

navigateUp() ve popBackStack()

NavController.navigateUp() fonksiyonunu çağırdığımızda aslında back veya up butonuna basmış gibi bir akış yürütülür. Kullanıcının bulunduğu ekranı stackten siler ve bir önceki ekranı navigate eder.

NavController.popBackStack() fonksiyonu da navigateUp ile aynı etkiyi gösterir. Sadece bu fonksiyon true yada false değeri döndürür. Eğer false dönerse stack sonuna gelindiği anlaşılır. Aşağıdaki gibi bir senaryoda kullanılabilir.

Nav Graph Tasarımı

Nav graph tasarlarken dikkat etmemiz gereken konu graph’ımızı olabildiğince mantıksal parçalara bölmektir. Başlangıçta bu konuya dikkat etmezsek bir süre sonra graphımız onlarca ekran ve action’dan oluşan arapsaçı olmuş bir görüntüye ulaşıyor. Yönetim inanılmaz zorlaşıyor ve nav graph’ımız açıldığında studio’un donduğuna şahit oluyoruz. Graph’ı düzgün bir hale çevirmeye kalktığımızda ise birçok ekranda refactor gerektiği için büyük bir iş gücüyle başbaşa kalıyoruz. Burada mantıksal parçalara nasıl böleceğimizden bahsetmek istiyorum. Uygulamamızda splash, login, register ve agreement ekranlarımızın olduğunu düşünelim. Bu 4 ekranı tek graph’da da kolaylıkla handle edebiliriz ama yukarıdaki sorunlarla ilerde karşılaşmak zorunda kalırız. Burada login, register ve sözleşme ekranlarını auth isminde bir graph içine koymalıyız. Bu graph’ı ya nested graph olarak splash ekranını da içeren graphta tanımlamalıyız. Ya da navigation klasörü içerisine farklı bir dosya oluşturup include etmeliyiz.

Nested Graph
Include Graph

Destinationlar arası veri iletimi

Bir destination’a tıkladığımızdaki yandaki arguments kısmından + butonuna tıklayarak argüman yapılandırmasını oluşturabiliriz. Örneğin kullanıcın premium olup olmadığını diğer ekrana göndermek için isPremium adında bir boolean argüman ekleyelim. Xml tarafında aşağıdaki şekilde bir görünüm oluşur.

Yukarıda homeFragment’ın isPremium adında bir argüman olacağını belirtiyoruz. Şimdi bu argümanın nasıl gönderileceği ve kullanılacağına bakalım.

En basit haliyle yukarıdaki şekilde veri aktarımı yapabiliriz. Ama google tarafından önerilen SafeArgs eklentiden bahsetmek istiyorum. Bu eklenti action’a sahip ekranlar için bazı kodlar üretir. Örneğin SplashFragment actionlara sahip diyelim. SplashFragmentDirections diye bir sınıf generate edilir. Argümanı alacak ekranında ProfileFragment olduğunun düşünelim. Bu sınıfın argümanlara ulaşması için de ProfileFragmentArgs şeklinde bir sınıf generate edilir. Bu sınıf action’larımıza ait fonksiyonları içerir. Eğer argüman isteyen bir ekrana olan bir action iste constructor’ında bu argümanı alır ve type-safe(tip güvenli) bir şekilde diğer ekrana argümanın iletilmesini sağlar.

Yukarıdaki şekilde projemize eklentiyi ekliyoruz. Kullanımı da aşağıdaki şekilde.

DeepLink Yönetimi

DeepLink ile birlikte uygulamamızdaki bir ekrana kullanıcı aksiyonu ile birlikte yönlendiriliriz. Örneğin; “https://example.com/product/1” linkine tıklandığında uygulamamızın ürün detay sayfasına gitmesini isteyebiliriz.(Implict Deeplink). Ya da uygulama içinden atılan bir bildirim veya widget içerisindeki bir butona tıkladığımızda uygulama içerisinde bir ekrana gitmek isteyebiliriz (Explict DeepLink). Navigation Component bu 2 deeplink yöntemini de kolayca yönetmemizi sağlar.

Implict deeplink: Graph’ımızda bir ekrana tıklağımızda sağ tarafta deeplink bölümünde +(artı) butonuna tıkladıktan sonra hangi url’in yakalanacağı belirttikten sonra deeplink’imiz ekleniyor.

Ayrıca manifeste de bir <nav-graph/> alanı eklememiz gerekiyor ki deeplink akışımız tamamlansın. Bu alan bizim için link yakalamak için gerekli intent-filter’ları ekleyecek. Kod örneği aşağıdaki şekilde.

Explict Deeplink’e gelirsek; kullanıcıya bir bildirim gösterdik ve bu bildirime tıkladığında sadece uygulamanın açılması değil de istediğimiz bir ekrana gitmesini istersek button click aksiyonu için normalde tanımladığımız PendingIntent’i aşağıdaki şekilde tanımlıyoruz.

Burada graph, destination ve argümanları tanımlıyoruz. Kullanıcı bildirime tıkladığında da belirttiğimiz graph’ın destination’ı açılır.

Sonuç

Yazımda Navigation Component’i, temelinde yatan mimariden başlayarak, Google resmi dokümanını ve projelerimde edindiğim tecrübeleri baz alarak genel olarak anlatmak istedim. Bazı konularda problemleri olan bir kütüphane olsa da bize kattığı birçok kolaylık var. Mimariye hakim olarak düzgün kullanıldığında gerçekten güzel işler çıkıyor. Ek olarak, bazı konu başlıklarının daha detay şekilde incelenmesi gerekiyor. Bundan sonraki Navigation yazılarımda daha spesifik konulara yer vermeye çalışacağım.

İyi Kodlamalar…

Kaynaklar:

--

--