Adım Adım Dagger2 / 3. Bölüm

Murat Karaöz
5 min readSep 8, 2018

--

Image from toptal.com

Scope ve Subcomponent

İlk iki bölümdeki dependency’lerin hepsinin scope değerini Singleton olarak atadık. Dagger varsayılan olarak sadece @Singleton scope değerini destekler. Bunun dışındaki scope değerleri kullanıcılar tarafından tanımlanmalıdır.

Bir dependency’nin @Singleton olarak işaretlenmesi ilgili sınıfın singleton olacağı anlamına gelmez. Yani @Singleton aslında bildiğimiz anlamda singleton nesneler yaratmaz. Bu şekilde işaretlenen nesneler bağlı oldukları component hayatta kaldığı sürece aynı değere sahip olurlar. Component her isteğe aynı nesneyi döner ancak component baştan yaratılırsa bize döndüğü nesnelerde sıfırlanır. 2. bölümdeki örnek uygulamada component sınıfı Application sınıfına bağlandığı için uygulama hayatta kaldığı sürece bize ilk yarattığı nesneleri döner.

Dagger ile daha kısıtlı life-cycle’ı olan elemanlar da yaratabiliriz. Örneğin bir dependecy Activity yada Fragment içinde inject edilip Activity öldüğünde bellekten silinebilir. Bunun bir örneğini aşağıdaki blogda bulabilirsiniz.

Üstteki blogda yazar bir github profil görüntüleme uygulaması kurgulamış. Uygulama kullanıcının repolarını listeliyor ve bir repoya tıklandığında yeni bir aktivite içinde repo detayları geliyor. Uygulamanın akışı aşağıdaki şekilde:

Bu yapıda 3 farklı yaşam döngüsü var.

  • Mavi kısım uygulama genelinde kullanılan global singleton değerleri gösteriyor. Mesela SharedPreferences.
  • Yeşil kısımda kullanıcı nesneleri var. Kullanıcı birden fazla ekranını açıp kapatabilir. Yani bir aktivite içinde kullanıldıktan sonra aktivite öldüğü halde bu nesne hayatta kalıp başka bir aktivite içinde kullanılmaya devam edebilir. Uygulamaya login olan kullanıcı gibi düşünebiliriz.
  • Turuncu kısımlar ise uygulamanın arayüzü, yani aktivite ve fragmentler. Sadece bu kısımda kullanılacak olan bağımlılıklar aktivite başlayınca değerini alıp aktivite sonlandığında bellekten silinmelidir. Yani sadece bağlı oldukları aktivite kadar hayatta kalmalıdırlar. Mesela Presenterlar bu tip nesnelerdir.

Bu yapı aşağıdaki şekilde özetlenebilir.

Singleton Scope: en uzu yaşayan scope. Uygulama başladıktan sonlanana kadar hayatta kalıyor.

User Scope: Application Scope’un sub-scope’u. Doğrudan Application Scope içinde tanımlanan nesnelere erişimi var. Bu scope github api’den kullanıcı bilgileri geldiği anda başlıyor ve kullanıcı değişene kadar ya da splash screen açılana kadar hayatta kalıyor.

Activity Scope: Sadece aktivite kadar yaşar ve hem User Scope’dan hem de Singleton Scope’dan nesnelere erişebilir.

Bunun nasıl gerçeklendiğini anlatmadan önce söylemeliyim ki User bileşeni gibi bileşenlerin bağımlılıklarını Dagger ile yönetmek bana karmaşık geldi. Bana göre sadece App seviyesi singletonlar ve doğrudan injection yapamadığım Activity gibi bileşenler için Dagger kullanmak mantıklı ama uygulamanın geri kalanı için Dagger kullanmadan constructor injection ile işimizi görebiliriz.

Bu yapıyı gerçeklemek için öncelikle user ve activity scope tanımlanmalıdır.

Aşağıda Application, User ve Activity modül sınıfları var. Hepsi kendi ihtiyaç duyduğu bileşenleri tanımlıyor. AppModule içinde uygulama geneli ihtiyaçlar varken, user modülü kullanıcıyı, activity modülü ise presenter, layoutmanager gibi aktivite içinde ihtiyaç duyulan şeyleri sağlıyor.

AppModule.java
UserModule.java
ActivityModule.java

Buraya kadar her şey normal, asıl bağımlılıklar component sınıfları içinde tanımlanıyor. En tepede olan AppComponent kendisine bağımlı olan componentlar için birer metot tanımlamalıdır. Bu metot genelde plus ya da plus+ComponentAdı şeklinde olur. Plus metodu sayesinde gradle UserComponent sınıfının bizim alt bileşenimiz olduğunu bilir.

UserModule sınıfı artık bir alt bileşen olduğu için tepesine @Component değil @Subcomponent annotation değeri gelir, scope olarak da daha önce tanımladığımız UserScope kullanılır. Ayrıca yine kendisine bağımlı olan componentlar için birer metot barındırır. Bu componentlar activitylere ait olan componentlar.

Activity componet da user component gibi kendi scope değeri ve subcomponent annotation’ı ile yaratılır.

Bu uygulama Google stili ile geliştirilmiş ve her activity için bir modül ve component sınıfı yazılmış.

Buraya kadar yaptığımız işlemler, 2. bölümde yaptıklarımıza oldukça benziyor. Bu noktada önemli olan UserScope ile çalışan UserComponent’ın nerede yaratılacağı.

Bu bileşen activity ve fragment life-cycle’ı dışında çalıştığı için Application sınıfı içinde yaratılmalıdır. Singleton scope uygulama ile başlayıp uygulama ile biter, activity scope activity ile başlar ve activity ile biter ama user scope’u bizim elle bitirmemiz gerekir. Bu nedenle application sınıfı içine bir de bu componenti silecek metot eklenmelidir.

ActivityScope ile sınırlı olan Componentlar activity içinde tanımlanacağı için activity ölünce onlarda garbage collector tarafından silinecektir. Bu nedenle activity scope için temizlik metodu yazmamıza gerek yoktur.

Scope ve Subcomponent Önemli Noktalar

  1. Eğer bir provides metodunun üzerinde scope yoksa (non-scoped) her inject işleminde yeni bir nesne yaratılır.
  2. Singleton scope ile tanımlı bir nesne ilk kez inject edileceği zaman yaratılır ve component içinde saklanır. Component yeniden yaratılmadığı sürece her inject işlemi için ilk yarattığı nesneyi kullanır.
  3. Kullanıcı tarafından yaratılan scope’lar fonksiyonel olarak singleton scope ile aynıdır. Sadece ait oldukları component nesnesinin ömrü daha kısadır.
  4. Eğer bir provide metodu üzerinde scope varsa onu kullanan component de aynı scope ile tanımlanmalıdır. Component’ın non-scope olabilmesi için modüllerinde hiçbir metodun üzerinde scope olmaması gerekir.
  5. Bir component’ın tüm modüllerinde provide metotları aynı scope değerini kullanmalıdır. Farklı scope değerleri olan modüller aynı component tarafından kullanılmamalıdır.
  6. Birbirine bağımlı iki component aynı scope değerine sahip olamaz. (Neden?)
  7. Scope’u olan nesnelerin injection işlemi thread-safe’dir.
  8. Parent component interface içinde subcomponentları döndüren getter metotları (plus) tanımlamak zorundadır.
  9. Subcomponent, parent component içindeki tüm nesnelere erişebilir. ve Bir subcomponent sadece tek bir parent component’a sahip olabilir.

Component Dependency

Componentlerı birbirine bağımlı hale getirmenin bir yolu da dependency tanımı yapmaktır. Genelde subcomponent kullanılır ancak bu yöntemi kullanmak isterseniz Dagger 2. Part II. Custom scopes, Component dependencies, Subcomponents makalesinde Component dependencies başlığı altında bunun nasıl yapıldığı anlatılmış.

Bu yöntemin SubComponent yönteminden en önemli farkı, parant componentın sub-componentın erişebileceği nesneleri interface içinde tanımlamasının zorunlu olmasıdır. SubComponent yönteminde parent içinde ne varsa sub ona erişebilir. Bu yöntemde bu erişim kısıtlanabilir.

Stackoverflow’da Dagger 2 subcomponents vs component dependencies başlığında bu ikisinin farkı tartışılmış.

Kaynaklar

--

--