Dagger Hilt: Difference between revisions

From bibbleWiki
Jump to navigation Jump to search
 
(3 intermediate revisions by the same user not shown)
Line 2: Line 2:
This page is to summarize new Dagger Hilt
This page is to summarize new Dagger Hilt
=Configuration=
=Configuration=
Dagger hilt relies on kotlin-kapt and dagger.hilt.android.plugin
Dagger hilt relies on kotlin-kapt and dagger.hilt.android.plugin. In the root build.gradle.kts
<syntaxhighlight lang="groovy">
<syntaxhighlight lang="groovy">
plugins {
plugins {
  id 'com.android.application'
    alias(libs.plugins.android.application) apply false
  id 'org.jetbrains.kotlin.android'
    alias(libs.plugins.kotlin.android) apply false
  id 'dagger.hilt.android.plugin'
    alias(libs.plugins.kotlin.compose) apply false
    alias(libs.plugins.android.library) apply false
    id("com.google.dagger.hilt.android") version "2.51.1" apply false
}
}
 
</syntaxhighlight>
In the app, domain or data build.gradle.kts
<syntaxhighlight lang="groovy">
plugins {
  id("kotlin-kapt")
  id("com.google.dagger.hilt.android")
}
...
dependencies {
dependencies {


Line 20: Line 29:
}
}
</syntaxhighlight>
</syntaxhighlight>
=Example Repository Implementation=
=Example Repository Implementation=
Here is the Implemented Repository
Here is the Implemented Repository
Line 143: Line 153:
}
}
</syntaxhighlight>
</syntaxhighlight>
==Example with Constructor==
Here is the bind which is the same as above
<syntaxhighlight lang="kotlin">
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
    @Binds
    abstract fun bindDataRepository(
        repository: DataRepository
    ): Repository
}
</syntaxhighlight>
Here is the network module to be injected into the Repository Implementation
<syntaxhighlight lang="kotlin">
@Module
@InstallIn(SingletonComponent::class)
class NetworkModule {
    @Provides
    @Singleton
    internal fun provideNetworkChecker(context: Application): NetworkChecker = NetworkChecker(context)
}
</syntaxhighlight>
Here is the implementation
<syntaxhighlight lang="kotlin">
class DataRepository @Inject
constructor(
    private val networkChecker: NetworkChecker
): Repository {
    override val isConnected: Boolean
        get() = networkChecker.isConnected
}
</syntaxhighlight>
=Services=
=Services=
For services we do not have a constructor but we do still have field injections. So we can do
For services we do not have a constructor but we do still have field injections. So we can do
Line 159: Line 203:
     return null
     return null
   }
   }
}
</syntaxhighlight>
=Lazy Injections=
Usually Injection happen on creation but you can make them happen at first use by using Lazy. E.g.
<syntaxhighlight lang="kotlin">
@HiltViewModel
class MyViewModel
@Inject constructor(
  private val repository: Lazy<MyRepository>
): ViewModel() {
}
}
</syntaxhighlight>
</syntaxhighlight>

Latest revision as of 21:56, 13 March 2025

Introduction

This page is to summarize new Dagger Hilt

Configuration

Dagger hilt relies on kotlin-kapt and dagger.hilt.android.plugin. In the root build.gradle.kts

plugins {
    alias(libs.plugins.android.application) apply false
    alias(libs.plugins.kotlin.android) apply false
    alias(libs.plugins.kotlin.compose) apply false
    alias(libs.plugins.android.library) apply false
    id("com.google.dagger.hilt.android") version "2.51.1" apply false
}

In the app, domain or data build.gradle.kts

plugins {
  id("kotlin-kapt")
  id("com.google.dagger.hilt.android")
}
...
dependencies {

  implementation "com.google.dagger:hilt-android:2.40.5"
  kapt "com.google.dagger:hilt-compiler:2.40.5"
  implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03"
  kapt "androidx.hilt:hilt-compiler:1.0.0"
  implementation ("androidx.hilt:hilt-navigation-compose:1.0.0")

}

Example Repository Implementation

Here is the Implemented Repository

class MyRepositoryImpl (
   private val api:MyApi
   private val appContext: Applicaiton
): MyRepository {

  override suspend fun doNetworkCall() {
  ....
}

Example Module

This is an example of an App Module. The InstallIn type decide how long the dependency will live. Examples are

  • ActivityComponent
  • ViewModelComponment
  • SingletonComponent
  • ActivityRetainedComponent Not destroyed when screen rotated
  • ServiceComponent

Note the @Provides is no different to Dagger 2, the @Singleton, not to be confused with the above, decides how many instances to create of this module. In this case we want one instance across the app. If we did not specify @Singleton it would create one on each @Inject request. Note this example also shows an example where the MyRepository depends on MyApi.

@Module
@InstallIn(SingletonComponent::class)
object AppModule {

  @Provides
  @Singleton
  fun provideMyApi(): MyApi {
     return Retrofit.Builder()
        .baseUrl("https://www.bibble.co.nz")
        .build()
        .create(MyApi::class.java)
  }

  @Provides
  @Singleton
  fun provideMyRepository(api: MyApi, app: Application): MyRepository {
    return MyRepositoryImpl(api, app)
  }
  
}

Example ViewModel

Now we are free to inject this into a ViewModel

@HiltViewModel
class MyViewModel 
@Inject constructor(
  private val repository: MyRepository
  private val appContext: Application
): ViewModel() {

}

Example Activity

@AndroidEntryPoint
class MainActivity: ComponentActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        MyTheme {
          val viewModel = hiltViewModel<MyViewModel>()
        }
    }
  }  
}

Example Application

This is a bit easier than the old days.You will need to provide the Class name in the Manifest as you did with Dagger 2 e.g. name=".MyApp"

@HiltAndroidApp
class Myapp: Application()

Duplicate Injecti ons

You can name injects where the return type is the same e.g.

@Provides
@Singleton
@Named("Hello1")
fun provideString1() = "Hello 1"

@Provides
@Singleton
@Named("Hello2")
fun provideString2() = "Hello 2"

Binds

This is the example using @Provides.

  @Provides
  @Singleton
  fun provideMyRepository(api: MyApi, app: Application): MyRepository {
    return MyRepositoryImpl(api, app)
  }

But we can achieve the same thing with bind. Where we are injecting an interface. This generates less code and seems to be the preferred approach

@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {

  @Binds
  @Singleton
  abstract fun bindMyRepository(
     myRepositoryImpl: MyRepositoryImpl
  ): MyRepository
}

We have to change the Repository Implementation to now inject the abstract class like so

class MyRepositoryImpl 
@Inject constructor(
   private val api:MyApi
   private val appContext: Applicaiton
): MyRepository {

  override suspend fun doNetworkCall() {
  ....
}

Example with Constructor

Here is the bind which is the same as above

@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {

    @Binds
    abstract fun bindDataRepository(
        repository: DataRepository
    ): Repository
}

Here is the network module to be injected into the Repository Implementation

@Module
@InstallIn(SingletonComponent::class)
class NetworkModule {
    @Provides
    @Singleton
    internal fun provideNetworkChecker(context: Application): NetworkChecker = NetworkChecker(context)
}

Here is the implementation

class DataRepository @Inject
constructor(
    private val networkChecker: NetworkChecker
): Repository {
    override val isConnected: Boolean
        get() = networkChecker.isConnected
}

Services

For services we do not have a constructor but we do still have field injections. So we can do

@AndroidEntryPoint
class MyService: Service() {
  @Inject 
  lazyinit var repository: MyRepository

  override onCreate() {
    super.onCreate()
    repository.doNetworkCall()
  }

  override fun onBind(p0: Intent?): IBinder? {
     return null
  }
}

Lazy Injections

Usually Injection happen on creation but you can make them happen at first use by using Lazy. E.g.

@HiltViewModel
class MyViewModel 
@Inject constructor(
  private val repository: Lazy<MyRepository>
): ViewModel() {

}