Dependency Injection (DI) is not just a good practice. It solves real problems. The more complex your app gets the more your Activities, Fragments, ViewModels and all kinds of services depend on other components to get their job done.
Initializing each dependency is tedious and can be forgotten (yay for NullPointerException). When it comes time for testing you realize that your carefully crafted classes are actually very hard to test properly and independently because of everything they rely on. Can it get worse?
Wouldn’t it be nice if your classes just list what they need and then it auto-magically appears?
Wouldn’t it be even better when during testing you can replace all dependencies with something simple?
Dependency Injection comes to the rescue. I will show you how simple it is, and in my next articles how good it really gets.
We are going to use Hilt, a very nice library (based on another cool library - Dagger) which makes working with DI on Android a breeze.
The Setup
Hilt is a library so you have to add it to your Android project. First you need to update your build.gradle.kts on Project level (kts is when you use the kotlin version of the gradle files) by adding this single line
plugins {
...
id("com.google.dagger.hilt.android") version "2.48" apply false
}
Then in your build.gradle.kts on App level you have to add the following few lines
plugins {
...
id("com.google.dagger.hilt.android")
}
...
dependencies {
...
implementation("com.google.dagger:hilt-android:2.48")
annotationProcessor("com.google.dagger:hilt-compiler:2.48")
androidTestImplementation("com.google.dagger:hilt-android-testing:2.48")
androidTestAnnotationProcessor("com.google.dagger:hilt-compiler:2.48")
}
Sync those changes. A few files will be downloaded and you are ready to go.
But the setup is not done yet.
Everything in Hilt is done with annotations. You are about to see the first one.
@HiltAndroidApp
public class MyApplication extends Application {
}
In case you don’t already have one, you need a custom application class. It doesn’t need anything complicated just the @HiltAndroidApp
at the top. That’s it.
Now let’s inform your AndroidManifest.xml
about the new application class.
<application
android:name=".MyApplication"
Now you are ready for the real deal. Wasn’t so complicated so far, was it?
Activities & Dependencies
Let’s create a very simple service.
public class SimpleService {
@Inject
public SimpleService() {
}
public String getText() {
return "Simple Service";
}
}
Notice the @Inject
annotation above. Every class that you want to inject somewhere needs one constructor annotated with @Inject
. This is how you tell Hilt what to use to create the class.
How do we use that in an Activity?
@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {
@Inject
public SimpleService simpleService;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
Log.d("MainActivity", simpleService.getText());
}
}
An activity which wants to use Dependency Injection itself or if its fragments want it, needs to be annotated with @AndroidEntryPoint
The @Inject
annotation above the simpleService property informs Hilt that you want that service to be injected into the activity. This is called property injection and it has one requirement. The property must be public.
We used only two annotations and the injection now just works. Magic!
Run the code above and you will see in your logs Simple Service.
The first time I did this I couldn’t believe how simple it is.
Fragments & series of DI
Dependency Injection works very well with fragments and navigation graphs. But first let us build something a little bit more interesting.
We are going to add one more service.
public class AdvancedService {
@Inject
public SimpleService simpleService;
@Inject
public AdvancedService() {
}
public String getText() {
return simpleService.getText() + " - Advanced Service";
}
}
It is very much like our first service, except that it actually depends on our first service. It used DI for the SimpleService
. This is getting interesting.
Now let’s add a fragment to our graph and a view model for it.
First, we will look into the view model code.
@HiltViewModel
public class InjectedViewModel extends ViewModel {
private final AdvancedService advancedService;
@Inject
public InjectedViewModel(AdvancedService advancedService) {
super();
this.advancedService = advancedService;
}
public String getText() {
return advancedService.getText() + " - InjectedViewModel";
}
}
There are a few things to unpack here. First the whole InjectedViewModel
is annotated with @HiltViewModel
. This way Hilt knows that it could use this for DI.
Second, we annotate the constructor with @Inject
, so that the DI knows which constructor is the right one.
This time, we don’t use property injection like the classes above. This time we use constructor injection. All you have to do is add everything you want injected as parameters of the constructor. Done.
How do we use all of that?
@AndroidEntryPoint
public class InjectedFragment extends Fragment {
private InjectedViewModel viewModel;
...
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
viewModel = new ViewModelProvider(this).get(InjectedViewModel.class);
binding.text.setText(viewModel.getText());
}
}
Like an Activity your Fragment is also annotated with @AndroidEntryPoint
. However, this time we don’t use property or constructor injection. It just doesn’t work that way for ViewModels. You have to use as usual a ViewModelProvider
but then the DI works its magic and provides all the dependencies requested by the view model.
You can run this code and you will get a nice and satisfying Simple Service - Advanced Service - InjectedViewModel showing you that on each step the correct class was injected at the right place and at the right time.
Just a few annotations and it all works.
Next
Today I showed you just the most common things about dependency injection in Android. Next time we will get a little bit deeper with scoping, non-constructor injection, interfaces, application context and more.
You can find all the code on GitHub
P.S. Google have created a small but extremely useful cheatsheet with Hilt annotations.