Android Hilt — Step By Step : Part1

Gözde Kaval
5 min readAug 9, 2020

--

Dagger was never easy to work with for Android developers. It is not made specifically by Android apps, therefore many lifecycle related things needs to be handled manually. Especially when the app becomes complex, Dagger can be really tricky/time consuming for most of the Android developers.

Finally, we have our new life saver, Hilt ! It is specifically made for Android dependency injection and comes with many alternatives comparing to Dagger:

  • Specifically made for Android, easier to understand and usage
  • Fits perfectly on lifecycle
  • Reduces Dagger boilerplate codes

Let’s try!

Implementation

  • Project root level build.gradle
ext.hilt_version = '2.28-alpha'
dependencies {
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
}
  • App level build.gradle
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'

dependencies {
implementation "com.google.dagger:hilt-android:2.28-alpha"
kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
}

Usage

  1. First we need to add @HiltAndroidApplication annotation on top Application. Otherwise Hilt components cannot be generated.
@HiltAndroidApp
class VacationApplication : Application()

2. Create a simple Logger class without any dependency. Then use it inside an Activity without any dependency injection.

class Logger {

fun log() {
Log.d("abc", "Logger(${this.hashCode()})")
}
}
//class MainActivity : AppCompatActivity() {

val logger: Logger = Logger()
override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)
logger.log()
}
}

3. We prefer to inject this dependency instead of creating instance inside activity.

We will follow this diagram to understand step by step

Step1:

  • Just inject Logger inside activity and run app:
class MainActivity : AppCompatActivity() {

@Inject
lateinit var logger: Logger
//...

Error 1 : `error: [Dagger/MissingBinding] com.example.vacation.hilt.Logger cannot be provided without an @Inject constructor or an @Provides-annotated method.`

  • What is that meaning ? It says, the system does not know how to create a Logger instance to provide us. Two ways to provide a dependency : Inject Constructor and @Provides Annotated method. This example, we will inject the constructor.
class Logger @Inject constructor()
  • Let’s run again. Now, we have success build but app crashed.

Error 2: Caused by: java.lang.IllegalStateException: Hilt Fragments must be attached to an @AndroidEntryPoint Activity. Found: class com.example.vacation.MainActivity

  • It says, we forgot to add @AndroidEntryPoint, what is it?
Marks an Android component class to be setup for injection with the standard Hilt Dagger Android components. Currently, this supports activities, fragments, views, services, and broadcast receivers.
  • If we want to use injected methods inside a class, it has to be setup with @AndroidEntryPoint annotation.
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

@Inject
lateinit var logger: Logger
//...
  • Now, app is working. Let’s check Logcat.
D/abc: Logger(207562636)

We succeed the first step!

Step2:

  • Let’s create another logger class Logger2 with activity parameter and inject constructor.
class Logger2 @Inject constructor(private val activity: FragmentActivity) {

fun log() {
Log.d("abc", "Logger2(${this.hashCode()}) : ${activity.localClassName}")
}
}
  • Inject Logger2 inside activity and call log() method on onCreate():
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

@Inject
lateinit var logger: Logger

@Inject
lateinit var logger2 : Logger2
  • Logcat:
D/abc: Logger(207562636)
D/abc: Logger2(81309397) : MainActivity
  • How it worked? We did not provide the activity as dependency. To understand better, we should follow generated codes. Start searching for Dagger<ApplicationName> and you will see generated class with this pattern. In my example, hilt generated class name is DaggerVacationApplication_HiltComponents_ApplicationC
  • Inside this class, we see ActivityCImpl which creates dependencies. If we follow injectMainActivity(..), we will see how we access activity.
@Override
public void injectMainActivity(MainActivity arg0) {
injectMainActivity2(arg0); // inject main activity
}
-private MainActivity injectMainActivity2(MainActivity instance) {
MainActivity_MembersInjector.injectLogger2(instance, getLogger2()); // inject logger2 instance to activity
return instance;
}
-
private
Logger2 getLogger2() {
return new Logger2(getFragmentActivity()); // create new logger2 instance
}
-
private FragmentActivity getFragmentActivity() // get activity
  • As seen, getFragmentActivity() method is called while generating activity dependency. If we inject Logger2 inside a fragment, it would also work.
MainFragment_MembersInjector.injectLogger2(instance, ActivityCImpl.this.getLogger2());
  • Hill supports activity that extendComponentActivity , such as FragmentActivity or AppCompatActivity. All fragments should extend androidx.Fragment

Step3:

  • Let’s one step ahead. Instead of activity, we want to create Logger with Context dependency.
class Logger3 @Inject constructor(private val context: Context) {

fun log() {
Log.d("abc", "Logger3(${this.hashCode()}) : ${context.packageName}")
}
}
  • Inject inside activity and log() method on onCreate().
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

@Inject
lateinit var logger: Logger

@Inject
lateinit var logger2: Logger2

@Inject
lateinit var logger3: Logger3
//...
}
  • When we run the app, this error occurred:

Error 1 : [Dagger/MissingBinding] android.content.Context cannot be provided without an @Provides-annotated method.

  • It means, even if activity has a context, it is not possible to use it directly. Also we cannot just inject it like previous steps because we have activity instance directly - not context.
  • Now, we need to create a function that creates the instance of Logger3 and annotate with @Provides Also to be able to use provides, it should be annotated with @Module
@Module
class LoggerModule {

@Provides
fun provideLogger3(context: Context): Logger3 = Logger3(context)
}
  • Lets run and see:

Error 2 : com.example.vacation.hilt.LoggerModule must also be annotated with @InstallIn.

  • What is InstallIn?
An annotation that declares which component(s) the annotated class should be included in when Hilt generates the components.
  • So what is component?
Each Hilt component is responsible for injecting its bindings into the corresponding Android class.
  • If we want to use a class bindings (Activity context in this case), it should be defined under the corresponding component type. Hilt provides many components that fits into Android lifecycle. In this case, we will use @ActivityComponent. Other component types are explained here.
  • Let’s add install in with activity component on top of module and run.
@InstallIn(ActivityComponent::class)
@Module
class LoggerModule {

@Provides
fun provideLogger3(context: Context): Logger3 = Logger3(context)
}

Error 3: [Dagger/MissingBinding] android.content.Context cannot be provided without an @Provides-annotated method.

  • Same issue happened again. But we defined our activity component, so why just cannot use its context ? Well, we still need to provide it. Thanks to Hilt, we can use predefined annotation to provide activity context. @ActivityContext
Annotation for a {@code Context} that corresponds to the activity.
  • Perfect, this is what we needed exactly! Let’s add and run again:
fun provideLogger3(@ActivityContext context: Context): Logger3 = Logger3(context)
  • Finally app is running, let’s check Logcat. We succeeded!
D/abc: Logger3(54888170) with context -> package name : com.example.vacation

Conclusion

Hilt makes Android developers life very easy even in a difficult subject : Dependency Injection.

  • Most of the boilerplates codes are changed with a well-named annotations(@AndroidEntryPoint).
  • Easy to understand the architecture. It has many Android specific benefits (@ActivityComponent, @ActivityContext).
  • Fully capable of using the lifecycles, Activity, Application, Fragment and more. On next article I will be explain more.

--

--