Dependency Injection(DI), programlamada yaygın olarak kullanılan ve bir sınıfın bağlı olduğu nesnelerin bağımlılıklarının sağlandığı bir design patterndir ve Android uygulama geliştirmede sıklıkla kullanılır. DI ilkelerini takip ederek test edilebilir, okunabilir, kolaylıkla yeni özellikler eklenebilir ve iyi bir mimari altyapıya sahip uygulamalar çıkarılabilir.
Android uygulama geliştirilirken genelde Dagger frameworku kullanılır bunun yanında Koin ve Kodein gibi alternatiflerde yer almaktadır. Dependency injectionı herhangi bir framework kullanmadan da manuel olarak yapma imkanı vardır. Ancak bu yöntem çok fazla boilerplate kod içereceğinden ve belli bir zaman sonra kontrolün zorlaşacağından herhangi bir DI frameworkü kullanmak işleri kolaylaştıracaktır.
Android uygulama geliştirirken oluşturulan classlar birçok bağımlılığa sahip olabilir. Bu bağımlılıkları azaltmak için bir DI frameworküne ihtiyaç vardır. Genellikle ilk akla gelen Google’ın geliştirmiş olduğu Dagger frameworküdür. Ancak özellikle junior geliştiriciler için öğrenilmesi ve uygulaması kolay olmayan bir framework olmasından ve zaman zaman boilerplate kodların fazlalaştığından dolayı son zamanlarda geliştiricilerin (ben de dahil) Dagger alternatiflerine yöneldikleri görülmektedir. Tam bu esnada Google Android ekibi, Jetpack içerisine daha kolay kullanıma sahip ve Dagger üstüne inşa edilen Hilt kütüphanesini bizlere sundu.
Dagger
Hilt, Dagger üzerine inşa edildiği için öncesinde Dagger’ın kullandığı yapıları bir hatırlayalım.
Dagger genel olarak 4 farklı annotation kullanarak DI işlemleri gerçekleştirir. Bunlar;
- @Module
- @Component
- @Provides
- @Inject
Basit anlamda daha iyi anlamak için @Module
annotationı bağımlılık sağlayıcısı olarak düşünün ve içerisinde bağımlıkları sağladığımız ve @Provides
ile belirttiğimiz bağımlılıklarımız olduğunu ve bir activity ya da başka bir sınıfı da consumer(tüketici) olarak düşünün. Örneğin activity içerisinde sağlanan bu sınıfı kullanmak istiyoruz. Kullanırken o sınıfı activity içerisine @Inject
etmemiz gerekiyor. Ayrıca sağlayıcıdan tüketiciye bağımlılık sağlamak için aralarında bir köprü oluşur, Dagger’da bu köprüler @Component
annotation ile belirlenir. İşte Dagger basit olarak bu şekilde çalışır tabi bu kadarla kısıtlı değil 🙂
Hilt
Hilt, Jetpack içerisine dahil edilen ve Android uygulamalarda DI için kullanılması önerilen ve Dagger üzerine inşa edilen bir kütüphanedir. Dagger’ın öğrenmesi ve uygulaması zor olmasından dolayı ve boilerplate kodlar içermesinden dolayı ortaya çıkmış bir kütüphanedir. Şimdi Hilt’i incelemeye başlayalım.
Öncelikle projenize dahil etmeniz gerekiyor. Bunun için alltaki kodları app/build.gradle
dosyasına ekliyoruz.
1 2 3 4 5 6 7 8 9 10 11 |
apply plugin: 'kotlin-kapt' apply plugin: 'dagger.hilt.android.plugin' android { ... } dependencies { implementation "com.google.dagger:hilt-android:{latest-version}" kapt "com.google.dagger:hilt-android-compiler:{latest-version}" } |
Daha sonra hilt-android-gradle-plugin
için uygulamanın build.gradle
dosyasına alttaki kodları ekliyoruz.
1 2 3 4 5 6 7 |
buildscript { ... dependencies { ... classpath 'com.google.dagger:hilt-android-gradle-plugin:{latest-version}' } } |
Hilt, Java 8 özelliklerini kullandığı için ayrıca projede Java 8 etkinleştiriyoruz.
1 2 3 4 5 6 7 |
android { ... compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } |
Şimdi ilk olarak Application classına bu projede Hilt kullandığımızı belirtmek için @HiltAndroidApp
annotation ekliyoruz.
1 2 |
@HiltAndroidApp class App : Application() |
Manifest dosyasına uygulamanızı eklemeyi unutmayın.
1 |
android:name=".App" |
Bu sayede Hilt’in kullandığı code generation işlemini aktif hale getirmiş olduk. Artık projemizde Hilt kullanabiliriz.
Android classlarının enjeksiyonu
@AndroidEntryPoint
annotation ile Android classlarına sağlanan bağımlıkları enjekte edebiliriz. Örneğin bir activityde;
1 2 |
@AndroidEntryPoint class MainActivity : AppCompatActivity() { ... } |
şeklinde kullanılabilir. Bu annotation Activity dışında Fragment, View, Service ve BroadcastReceiver classlarını da desteklemektedir. Artık bu activity içerisinde provide edilmiş bir classı @Inject
ile kullanabiliriz.
1 2 3 4 5 6 |
@AndroidEntryPoint class MainActivity : AppCompatActivity() { @Inject lateinit var repository: UserRepository ... } |
Hilt Components
Componentler temelde uygulamanın farklı parçalarıdır ve modüllere bağlıdırlar. Dagger’da component yapısı biraz karmaşık ve anlaşılması zordur. Ancak Hilt ile component oluşturmak yerine Hilt içerisinde yer alan hazır componentleri kullanacağız ve bu bizim elimizi oldukça hızlandıracak.
Hilt içerisinde yer alan componentlerin belirli bir scope vardır. Örneğin ActivityComponent
bir @ActivityScoped
dur.
Hilt içerisinde yer alan componentler ve nerede inject edilecekleri aşağıdaki gibidir.
ApplicationComponent | Application |
ActivityRetainedComponent | ViewModel |
ActivityComponent | Activity |
FragmentComponent | Fragment |
ViewComponent | View |
ViewWithFragmentComponent | View annotated with @WithFragmentBindings |
ServiceComponent | Service |
Hilt Modules
Provide edilecek olan bağımlılıkların yer aldığı classlar aynı Dagger’daki gibi @Module
annotation ile yapılır. Kullanılan componenti belirtmek için ise Hilt ile gelen @InstallIn
annotation kullanılır. Bu annotation parametre olarak yukarıdaki hazır Hilt componentlerinden (kullanılacağı yere göre) birini alır. Örneğin;
1 2 3 4 5 6 |
@Module @InstallIn(ApplicationComponent::class) object AppModule { @Provides fun provideApiService(): ApiService {...} } |
Yukarıdaki class ile bir modüle oluşturduk, içerisinde tüm uygulamada kullanılacak olan bir bağımlılığı @Provides
annotation ile uygulamaya sağladık ve bu bağımlılığı tüm uygulamada kullanacağımız için @InstallIn
annotationa parametre olarak ApplicationComponent
verdik. Eğer activity için provide etmek isteseydik o zaman ActivityComponent
kullanacaktık.
Hilt & Dagger Annotations Cheat Sheet
https://developer.android.com/training/dependency-injection/hilt-android