Android Hilt Dependency Injection Kullanımı -2

Mustafa Süleyman Kınık
10 min read1 hour ago

--

Herkese merhaba,
Hilt Dependency Injection Kullanımı serisinin ikinci yazısıyla konumuza devam ediyoruz. Önceki yazıyı okumadıysanız, konu bütünlüğü ve kullanılan örneklerin devamlılığı adına buraya başlamadan önce ilk yazıyı okumanızı tavsiye ederim.

Hilt Component Kullanımı

Hilt Component’leri, inject edilen yapıların, karşılığı olan Android sınıflarına inject etme işleminin sorumlusudur. InstallIn içinde yazdığımız “SingletonComponent::class” buna örnektir.
Her component’in bir sorumluluk alanı bulunmaktadır. Aşağıdaki tabloda bunu görebilirsiniz.

SingletonComponent Application’ı kapsarken, ActivityComponent Activity’leri kapsamaktadır.
Peki bu kapsamadan kastımızı biraz daha açarsak:
Hilt, bu componentleri kapsadığı alanların lifecycle’larına göre otomatik olarak generate edilmiş instance’ları yaratır ve yok eder. Yani özetle bu generate edilen componentlerin kullanım ömürleri, ilgili Android class’ların lifecycle’larına bağlıdır. Bunu aşağıdaki tabloda görebilirsiniz.

Tabloya istinaden örnek vermek gerekirse, iki adet olan adları AActivity ve BActivity adında class’larımız olsun. AActivity’nin kendisine ait bir component instance’ı olacaktır. Bu AActivity’sinden BActivity’sine gittiğimizde ise bu sefer de BActivity’sine ait yeni bir component instance’ı aktif olmuş olacaktır. Geri işlemi yapıp BActivity’si destroy olduğunda ise bu destroy olan activity’nin component instance’da destroy olacak ve geriye sadece ayakta kalan AActivity ve onun component instance’ı kalacaktır. Ayriyeten yukarıdaki tablolara istinaden her component tabloda karşılığı olan kısım için çalışmaktadır. Yani ViewModelComponent ile oluşturulmuş bir module’un function’ı veya bu component’in scope’unu kullanan bir class, Activity veya Fragment tarafından inject edilemez. Keza tam tersi FragmentComponent için geçerlidir. Bu konu ile ilgili bazı istisnalar da bulunmaktadır. Bunların ne olduğunu da Hilt component hiyerarşisi başlığı altında açıklayacağız.

@Module
@InstallIn(ActivityComponent::class)
abstract class ActivityModule {
@Binds
abstract fun bindActivitySessionCounterRepository(impl: ActivitySessionCounterRepositoryImpl): ActivitySessionCounterRepository
}
class MainActivity : ComponentActivity() {

@Inject
lateinit var activitySessionCounterRepository: ActivitySessionCounterRepository

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activitySessionCounterRepository.increment()
println("activitySessionCounterManager.counter = ${activitySessionCounterRepository.getCounter()}")
setContent {
HiltProjectTheme {
NavigationHost()
}
}
}
}

Yukarıda ActivityModule içinde instance’ını sağladığımız ActivitySessionCounterRepository’sini MainActivity‘de çağırdımızda çalıştığını göreceksiniz. Fakat aşağıdaki gibi ViewModel içinde çağırmak istersek compile error aldığımızı göreceksiniz.

class FirstScreenViewModel @Inject constructor(
private val activitySessionCounterRepository: ActivitySessionCounterRepository
) :
ViewModel() {
init {
activitySessionCounterRepository.increment()
println("activitySessionCounterManager.counter = ${activitySessionCounterRepository.getCounter()}")
}
}

Veya tam tersi bir şekilde aşağıda yazdığımız kodu MainActivity içine inject etmek istersek yine compile error alacağız.

@Module
@InstallIn(ViewModelComponent::class)
abstract class ViewModelModule {
@Binds
abstract fun bindViewModelSessionCounterRepository(impl: ViewModelSessionCounterRepositoryImpl): ViewModelSessionCounterRepository
}

Fakat aşağıdaki gibi SingletonComponent kullansaydık yazının ilerisinde hiyerarşi başlığında göreceğiniz üzere error almayacaktık. Çünkü SingletonComponent Application seviyesindedir. Yani bütün app’i kapsar ve bu sayede Activity, Fragment, ViewModel ve kendi oluşturduğunuz class’lar içinde inject işlemi yapabilirsiniz.

@Module
@InstallIn(SingletonComponent::class)
class AppModule {
@Provides
@Singleton
fun provideGson() = Gson()
}

Her component ilgili class’ın lifecycle’ına bağlı demiştik ve örnek ile de pekiştirmiştik. Bu durumu ViewModel kodlarımız üzerinden bakarsak:

class FirstScreenViewModel @Inject constructor(
private val viewModelSessionCounterRepository: ViewModelSessionCounterRepository
) :ViewModel() {
init {
viewModelSessionCounterRepository.increment()
println("viewModelSessionCounterRepository.getCounter()= ${viewModelSessionCounterRepository.getCounter()}")
}
}
@HiltViewModel
class SecondScreenViewModel @Inject constructor(
private val viewModelSessionCounterRepository: ViewModelSessionCounterRepository,
) : ViewModel() {
init {
viewModelSessionCounterRepository.increment()
println("viewModelSessionCounterRepository.getCounter() = ${viewModelSessionCounterRepository.getCounter()}")
}
}

Yukarıdaki iki ViewModel’de aslında aynı module’den inject yaptıkları halde farklı component instance’ları kullanmaktadır.
Fakat aşağıdaki gibi AppModule içinden bu ViewModel’lara inject işlemi yapsaydık, kullanılacak component instance aynı olacaktı. Çünkü yukarıda da belirtiğimiz gibi bizim Application class’ımızla birlikte instance ayağa kalacak ve Application class’ımızın ölmesiyle de son bulacaktı.

@Module
@InstallIn(SingletonComponent::class)
class AppModule {
@Provides
@Singleton
fun provideGson() = Gson()
}

Hilt Scope Kullanımı

Inject işlemi gerçekleşirken bu inject olan yapılar varsayılan olarak scope’ları yoktur. Yani spesifik olarak bir class’ın yada module içinde tanımlı olan bir function’ın üstünde scope annotation’ı belirtmediğimiz takdirde scope’ları da olmayacaktır.
Scope kullanımlarının en önemli noktası ve hatta ana işlevi, injection işlemleri ile bize sağlanan instance’ların yönetimidir.
Yukarıda bahsettiğimiz her component’in kendine özgü scope’ları bulunmaktadır. Bunu aşağıdaki tabloda görebilirsiniz.

Bunun anlamı belirli bir component’i sadece kendi scope’u ile kullanabileceği anlamına gelmektedir. Yani SingletonComponent’in bir binding’i(inject edilecek olan) ViewModelScoped veya ActivityScoped alamaz. Yada tabloda gördüğünüz üzere scope’lar belirli Android class’ları ile component’ler gibi eşleşmektedir. @FragmentScoped ile işaretlenmiş bir class veya module function’ı, ViewModel içinde inject edilemez.

@Module
@InstallIn(SingletonComponent::class)
class AppModule {
@Provides
@Singleton
fun provideGson() = Gson()
}

Yanlış yerde scope kullanımlarında ise yukarıdaki örnekten yola çıkarak, InstallIn içinde bulunan SingletonComponent’i ViewModelComponent::class ile değiştirirsek veya SingletonComponent::class ile @ActivityScoped kullanırsak compile error alırız.
Scope’ları kullanmak için illa bir module içinde uygulamamıza gerek yoktur. Constructor injection yaptığımız class’larımızda da scope’ları kullanabiliriz.

@Singleton
class SessionCounterManager @Inject constructor() {
var counter = 0
private set

fun increment() {
counter++
}
}
@FragmentScoped
class FragmentSessionCounterWithScopeManager @Inject constructor(){
var counter = 0
private set

fun increment() {
counter++
}
}

Yukarıda scope’lar için “ injection işlemleri ile bize sağlanan instance’ların yönetimidir” demiştik. Scope’ların instance’larla olan ilişkisine örnekler üzerinden bakarsak:

@Singleton
class SessionCounterManager @Inject constructor() {
var counter = 0
private set

fun increment() {
counter++
}
}
class MainActivity : ComponentActivity() {

@Inject
lateinit var sessionCounterManager: SessionCounterManager

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sessionCounterManager.increment()
println("MainActivity sessionCounterManager.counter = ${sessionCounterManager.counter}")
enableEdgeToEdge()
setContent {
HiltProjectTheme {
NavigationHost()
}
}
}
}
@HiltViewModel
class FirstScreenViewModel @Inject constructor(
private val sessionCounterManager: SessionCounterManager
) :
ViewModel() {
init {
sessionCounterManager.increment()
println("FirstScreenViewModel sessionCounterManager.counter = ${sessionCounterManager.counter}")
}
}
@HiltViewModel
class SecondScreenViewModel @Inject constructor(
private val sessionCounterManager: SessionCounterManager
) : ViewModel() {
init {
sessionCounterManager.increment()
println("SecondScreenViewModel sessionCounterManager.counter = ${sessionCounterManager.counter}")
}
}

Yukarıda @Singleton scope’u kullanılmış class’ı MainActivity, FirstScreen ve SecondScreen composable’larının ViewModel’larında inject ettiğimizde alacağımız çıktı aşağıdaki gibi olacaktır.

MainActivity sessionCounterManager.counter = 1
FirstScreenViewModel sessionCounterManager.counter = 2
SecondScreenViewModel sessionCounterManager.counter = 3

Bunun sebebi @Singleton scope annotation’ını kullandığımızda, bu manager class’ını inject etmek isteyen Activity ve ViewModel’lerimiz scope ile her seferinde aynı instance’a erişebilmelerindendir.
Bu scope’u kullanmadan inject etmek istersek, her seferinde inject eden yerler bu sefer aynı instance’ı kullanmayacağı için bizim counter değerimiz aşağıdaki gibi 1 olur.

class SessionCounterManager @Inject constructor() {
var counter = 0
private set

fun increment() {
counter++
}
}
MainActivity sessionCounterManager.counter = 1
FirstScreenViewModel sessionCounterManager.counter = 1
SecondScreenViewModel sessionCounterManager.counter = 1

Burada dikkat edilmesi gereken nokta, örnekte bulunan inject etme işlemi yapan Activity ve ViewModel’lerimiz SingletonComponent’in aynı instance’ına erişirken birlikte @Singleton scope’u kullandığında, bu component’in instance’ı ile inject edilen manager class’ın da aynı instance’ını kullanılırken , bu scope’u kullanmadığında ise aynı component instance’ını kullansa da inject edilen instance’ların farklı olmasıdır.
Konunun daha iyi anlaşılması için @ViewModelScoped üzerinden örneklere bakarsak:

@ViewModelScoped
class ViewModelSessionCounterWithScopeManager @Inject constructor(){
var counter = 0
private set

fun increment() {
counter++
}
}
@HiltViewModel
class FirstScreenViewModel @Inject constructor(
private val viewModelSessionCounterWithScopeManager: ViewModelSessionCounterWithScopeManager,
private val viewModelSessionCounterWithScopeManager2: ViewModelSessionCounterWithScopeManager,
) :
ViewModel() {
init {
viewModelSessionCounterWithScopeManager.increment()
println("FirstScreenViewModel viewModelSessionCounterWithScopeManager.counter = ${viewModelSessionCounterWithScopeManager.counter}")
viewModelSessionCounterWithScopeManager2.increment()
println("FirstScreenViewModel viewModelSessionCounterWithScopeManager2.counter = ${viewModelSessionCounterWithScopeManager2.counter}")
}
}
@HiltViewModel
class SecondScreenViewModel @Inject constructor(
private val viewModelSessionCounterWithScopeManager: ViewModelSessionCounterWithScopeManager,
private val viewModelSessionCounterWithScopeManager2: ViewModelSessionCounterWithScopeManager,
) : ViewModel() {
init {
viewModelSessionCounterWithScopeManager.increment()
println("SecondScreenViewModel viewModelSessionCounterWithScopeManager.counter = ${viewModelSessionCounterWithScopeManager.counter}")
viewModelSessionCounterWithScopeManager2.increment()
println("SecondScreenViewModel viewModelSessionCounterWithScopeManager2.counter = ${viewModelSessionCounterWithScopeManager2.counter}")
}
}

Kodumuzun çıktısı

FirstScreenViewModel viewModelSessionCounterWithScopeManager.counter = 1
FirstScreenViewModel viewModelSessionCounterWithScopeManager2.counter = 2
SecondScreenViewModel viewModelSessionCounterWithScopeManager.counter = 1
SecondScreenViewModel viewModelSessionCounterWithScopeManager2.counter = 2

@Singleton annotation’ından farklı bir çıktı aldığımızı fark etmişsinizdir. Inject edilecek class’ımızda @Singleton kullansaydık counter’ımız sırasıyla “1,2,3,4” olacaktı. Aslında cevabımızın böyle olması scope ile ilgili de değil component ile ilgilidir. Yani bizler inject edilecek olan class’ta @ViewModelScoped kullandığımızda, aslında component’imizde yukarıdaki tabloda göreceğiniz üzere ViewModelComponent olmuş oldu. Bu sebeple FirstScreenViewModel’de aynı class’ı 2 ayrı constructor parametresi olarak inject ettiğimizde bu viewmodel hala aynı component’in intance’ını kullandığından, kullandığımız scope sayesinde de bize her seferinde aynı instance’ı sağlamış oldu. Yani üçüncü bir parametreyi de inject etseydik, bu sefer de FirstViewModel için counter’ımız en son “3” değerine ulaşmış olacaktı. SecondViewModel’e geçtiğimizde ise bu ViewModel bizim class’ımızı inject etmek istediğinde yeni bir ViewModelComponent instance’ı kullanacağı için haliyle inject ettiğimiz class’ta bu ViewModel içinde ilk kez ele alınmış olunuyor. Bu sebeple counter’ı artırdığımız zaman tekrardan “1” değerini almış oldu.
Özetle scope kullanımında sağlanan eşsiz instance, o scope’un bulunduğu component instance’ının yaşam döngüsüne bağlıdır. Ve her eşsiz instance her component instance’ı için birer kez yaratılır. Burada iki adet ViewModel için aslında iki adet component instance’ı ve bu iki adet component instance’ı içinde iki adet inject edilen class’ın instance’ı bize sağlanmış oldu.
Singleton’da neden böyle olmadı diye sorarsanız aslında bunun sebebi component konusunda paylaştığımız tablodan fark edebilirsiniz. SingletonComponent Application class’ını baz aldığı için ve projemizde bundan bir tane olduğu için @Singleton scope’unu kullandığımız her yerde bize, inject edilen class’ın aynı istance’ı sağlanmış oldu.
Scope olmadan inject işlemi yaptığımızda da, her seferinde yeni bir instance kullanmış oluruz.

class ViewModelSessionCounterWithScopeManager @Inject constructor(){
var counter = 0
private set

fun increment() {
counter++
}
}
FirstScreenViewModel viewModelSessionCounterWithScopeManager.counter = 1
FirstScreenViewModel viewModelSessionCounterWithScopeManager2.counter = 1
SecondScreenViewModel viewModelSessionCounterWithScopeManager.counter = 1
SecondScreenViewModel viewModelSessionCounterWithScopeManager2.counter = 1

Görüldüğü üzere sadece scope’u silip tekrardan sonuçlara bakarsak, aynı ViewModel’lar içinde dahi counter’ın değerinin hep “1” olduğunu yani her seferinde başka bir instance kullanıldığını görebilirsiniz.
Buraya kadar olan örnekleri hep constructor injection üzerinden verdik. Module ile kullanımda da anlatılanlar dışında bir fark olmayacaktır.

class ViewModelSessionCounterRepositoryImpl @Inject constructor(): ViewModelSessionCounterRepository {
private var counter = 0

override fun getCounter(): Int = counter

override fun increment() {
counter++
}
}
@Module
@InstallIn(ViewModelComponent::class)
abstract class ViewModelModule {
@Binds
@ViewModelScoped
abstract fun bindViewModelSessionCounterRepository(impl: ViewModelSessionCounterRepositoryImpl): ViewModelSessionCounterRepository
}

Yine aynı şekilde iki ViewModel’imizde de ikişer kez çağırdığımızda çıktımız aynı olacaktır.

FirstScreenViewModel viewModelSessionCounterRepository.getCounter() = 1
FirstScreenViewModel viewModelSessionCounterRepository2.getCounter() = 2
SecondScreenViewModel viewModelSessionCounterRepository.getCounter() = 1
SecondScreenViewModel viewModelSessionCounterRepository2.getCounter() = 2

Scope’u sildiğimizde de scopesuz constructor injection kullanımındaki gibi aynı sonucu göreceğiz.

@Module
@InstallIn(ViewModelComponent::class)
abstract class ViewModelModule {
@Binds
abstract fun bindViewModelSessionCounterRepository(impl: ViewModelSessionCounterRepositoryImpl): ViewModelSessionCounterRepository
}
FirstScreenViewModel viewModelSessionCounterRepository.getCounter() = 1
FirstScreenViewModel viewModelSessionCounterRepository2.getCounter() = 1
SecondScreenViewModel viewModelSessionCounterRepository.getCounter() = 1
SecondScreenViewModel viewModelSessionCounterRepository2.getCounter() = 1

Hilt Component Hiyerarşisi

Hilt Component’leri arasında bir hiyerarşi bulunmaktadır. Bu hiyerarşiye göre child olan component’in kullanıldığı yerde, aynı zamanda bu child component’in parent’ı da kullanabilmektedir.

Tabloda da görülebileceği gibi bir ViewModelComponent veya ActivityComponent SingletonComponent’in child’ı, FragmentComponent ise hem SingletonComponent hem de ActivityComponent’in child’dır. Buna örnek olarak yazımızda sık sık gördüğünüz AppModule’de bir SingletonComponent’dir. Bu module içinde bulunan Gson’ı Activity, Fragment ve ViewModel içine inject edebilmiştik. Aşağıda daha farklı örnekle anlatımımızı pekiştirelim.

@ActivityScoped
class ActivitySessionCounterWithScopeManager @Inject constructor() {
var counter = 0
private set

fun increment() {
counter++
}
}
@AndroidEntryPoint
class MainFragmentsActivity : AppCompatActivity() {

@Inject
lateinit var activitySessionCounterWithScopeManager: ActivitySessionCounterWithScopeManager

@Inject
lateinit var activitySessionCounterWithScopeManager2: ActivitySessionCounterWithScopeManager

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main_fragments)
activitySessionCounterWithScopeManager.increment()
println("MainFragmentsActivity activitySessionCounterWithScopeManager.counter = ${activitySessionCounterWithScopeManager.counter}")
activitySessionCounterWithScopeManager2.increment()
println("MainFragmentsActivity activitySessionCounterWithScopeManager2.counter = ${activitySessionCounterWithScopeManager2.counter}")
}
}
@AndroidEntryPoint
class FirstFragment : Fragment() {
private val viewModel: FactoryExampleViewModel by activityViewModels { FactoryExampleViewModel.Factory }

@Inject
lateinit var activitySessionCounterWithScopeManager: ActivitySessionCounterWithScopeManager

@Inject
lateinit var activitySessionCounterWithScopeManager2: ActivitySessionCounterWithScopeManager

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_first, container, false)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
activitySessionCounterWithScopeManager.increment()
println("FirstFragment activitySessionCounterWithScopeManager.counter = ${activitySessionCounterWithScopeManager.counter}")
activitySessionCounterWithScopeManager2.increment()
println("FirstFragment activitySessionCounterWithScopeManager2.counter = ${activitySessionCounterWithScopeManager2.counter}")

}

}

Örneğimiz için yine counter işlemi yapan constructor injection ile inject edilebilir bir class oluşturduk. Bu class’ın başına @ActivityScoped scope’unu ekledik. Böylece her ActivityComponent instance‘ı için inject edilecek class’ın bir instance’ını bize sağlıyor olacaktır.
Daha sonrasında bu yazılan kodları denediğimizde proje başarılı bir şekilde compile olduğunu göreceksiniz. Yazdırdığımız çıktılara baktığımızda da sonuç aşağıdaki gibi olacaktır.

MainFragmentsActivity activitySessionCounterWithScopeManager.counter = 1
MainFragmentsActivity activitySessionCounterWithScopeManager2.counter = 2
FirstFragment activitySessionCounterWithScopeManager.counter = 3
FirstFragment activitySessionCounterWithScopeManager2.counter = 4

Bunun sebebi yukarıda da bahsettiğimiz gibi @ActivityScoped scope’unu kullandığımızda, bize her ActivityComponent instance’ı için inject edilenin bir instance’ını sağlamasından kaynaklıdır. Yani Fragment’ımız Activity içinde host edildiğinde bu Fragment’ın inject ettiği yapı hala aynı Activity’in component instance’ını kullanmakta ve scope sayesinde de bu component instance’ı için her seferinde inject edilen class’ın aynı instance’ı bizlere sağlanmaktadır. Peki aynı örneği scope kullanmadan denersek ne olur derseniz:

class ActivitySessionCounterRepositoryImpl @Inject constructor(): ActivitySessionCounterRepository {
private var counter = 0

override fun getCounter(): Int = counter

override fun increment() {
counter++
}
}
@Module
@InstallIn(ActivityComponent::class)
abstract class ActivityModule {
@Binds
abstract fun bindActivitySessionCounterRepository(impl: ActivitySessionCounterRepositoryImpl): ActivitySessionCounterRepository
}

Bu sefer abstract yapılarımızı inject edebilmek için kullandığımız @Binds annotation kullanan bir module oluşturuyoruz. Ve yine aynı şekilde ActivityComponent kullanıyoruz. Ve bunları benzer şekilde inject ediyoruz.

class MainFragmentsActivity : AppCompatActivity() {
@Inject
lateinit var activitySessionCounterRepository: ActivitySessionCounterRepository

@Inject
lateinit var activitySessionCounterRepository2: ActivitySessionCounterRepository

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main_fragments)
activitySessionCounterRepository.increment()
println("FirstFragment activitySessionCounterManager.getCounter() = ${activitySessionCounterRepository.getCounter()}")
activitySessionCounterRepository2.increment()
println("FirstFragment activitySessionCounterRepository2.getCounter() = ${activitySessionCounterRepository2.getCounter()}")

}
}
@AndroidEntryPoint
class FirstFragment : Fragment() {
private val viewModel: FactoryExampleViewModel by activityViewModels { FactoryExampleViewModel.Factory }

@Inject
lateinit var activitySessionCounterRepository: ActivitySessionCounterRepository

@Inject
lateinit var activitySessionCounterRepository2: ActivitySessionCounterRepository

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_first, container, false)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
activitySessionCounterRepository.increment()
println("FirstFragment activitySessionCounterManager.getCounter() = ${activitySessionCounterRepository.getCounter()}")
activitySessionCounterRepository2.increment()
println("FirstFragment activitySessionCounterRepository2.getCounter() = ${activitySessionCounterRepository2.getCounter()}")
}

}

Çıktımıza bakarsanız scope kullanmadığımız için aslında beklediğimiz şekilde her inject ettiğimiz yerde farklı bir instance kullanıldığı için counter değerlerini “1” olarak görmekteyiz.

FirstFragment activitySessionCounterManager.getCounter() = 1
FirstFragment activitySessionCounterRepository2.getCounter() = 1
FirstFragment activitySessionCounterManager.getCounter() = 1
FirstFragment activitySessionCounterRepository2.getCounter() = 1

Peki bu hiyerarşiye uymayıp tam tersi bir şekilde FragmentComponent’i Activity için ne olur derseniz. Component konusunda da anlattığımız gibi compile error alacağız.

Ön Tanımlı Qualifier’ler ve Binding’ler

Hilt ön tanımlı bazı qualifer’lere sahiptir. Bunlara örnek olarak @ApplicationContext ve @ActivityContext verebiliriz.

class AnalyticsServiceImpl @Inject constructor(
@ApplicationContext context: Context
) : AnalyticsService { ... }
@Module
@InstallIn(SingletonComponent::class)
class AppModule {
@Provides
@Singleton
fun provideCircularProgressDrawable(@ApplicationContext context: Context): CircularProgressDrawable {
return CircularProgressDrawable(context).apply {
strokeWidth = 5f
centerRadius = 30f
start()
}
}
}

Yukarıdaki örneklerde görüleceği üzere context’e hazır bir şekilde erişebilirsiniz.
Aynı şekilde yine daha önceden tanımlı binding’lere(inject edilebilir yapılara) kendiniz öncesinde herhangi bir işlem yapmadan erişebilirsiniz.
Aşağıdaki tabloda component’lerin varsayılanlarını görebilirsiniz.

@HiltViewModel
class SecondScreenViewModel @Inject constructor(private val savedStateHandle: SavedStateHandle) :
ViewModel() {
init {
val arg = savedStateHandle.get<String>(EXAMPLE_ARG)
println("arg = $arg")
}
}

Yukarıda yazdığım ViewModel’de görebileceğiniz üzere direkt olarak SavedStateHandle’ı constructor’a ekledim ve kendisi ile ilgili inject edebilmek için hiçbir işlem yapmadım. Daha sonrasında FirstScreen’den SecondScreen’e geçerken, argument olarak string bir değer gönderdiğimde init içinde savedStateHandle ile bu değere ulaşabildim. Burayı kalabalıklaştırmamak adına aşağıda paylaşacağım Github reposundan ViewModel harici diğer kısımlarda yazılan kodlara da bakabilirsiniz.

Yazı dizimizin burada son bulmaktadır. Yazımızda kullandığımız kod örneklerine tek bir projede toplanmış halde aşağıdaki GitHub reposundan erişebilir ve verilen örnekleri kendiniz deneyebilirsiniz.

Kaynakça

https://developer.android.com/training/dependency-injection
https://dagger.dev/
https://developer.android.com/codelabs/android-hilt#0

--

--