The first time I came across dependency injection was when I started using a framework called Spring which is now commonly known as Spring Boot. That was many years ago and as many Java developers crossed over to Android they brought the best practice of using dependency injection with them. However, when using dependency injection in a resource constraint environment such as a mobile device, speed and efficiency become very important. The Spring Java backend applications that I used to write would sometimes take 5 to 10 seconds to start up, making the use of Spring impractical on a mobile device.
In the beginning, there weren’t many dependency injection frameworks for Android but that has changed over time and now we are spoiled for choice. If you are using dependency injection on Android you have probably heard of Dagger2, Koin, and Kodein but there is also another great library out there called Toothpick 3. I think developers should seriously consider this library as it has a lot to offer.
Having used Dagger2 and played around with Koin and Kodein I believe that Toothpick 3 is the best in terms of simplicity and ease of use not to mention testability. In a nutshell Toothpick 3 is slightly different from the others as it uses a scoped tree to decide with dependencies to inject. When we think about scopes in Android, classes such as
ViewModel should come to mind as each of these classes has a life cycle. I will use child scopes and sub scopes interchangeable but they refer to the same thing. In a scoped tree, a child scope can
- inherit the dependencies from its parent
- override a dependency from its parent
- add additional dependencies
- may have a shorter live span than its parent
I have created a simple app on GitHub which shows you how to configure and use Toothpick 3. To keep things simple, it only demonstrates how to configure 2 different scopes, application and activity however it could be easily extended to support other common scopes such as Fragments or ViewModels.
As you can see the sample application has 3 scopes
- The root scope where nothing is installed and is used to signify the start of the tree
- The APP scope where classes such as the
AppConfigManagercan be used by this scope or any child scope
- The activity scope where classes will exist only while the activity is alive. These classes can’t be used outside of this scope.
The scope tree means that classes defined in the Application scope would be long-lived while the app is open while classes defined in the activity scope would have a much shorter life span.
The AppModule is installed in the
APPSCOPE and the application is injected with the required dependencies. Scopes are just simple Strings.
AppModule contains the following dependencies. At work, we use a combination of inline modules and predefined classes like this one.
In the following activity we inject the
PayBillManager into the activity scope. This scope will be closed when the activity is destroyed allowing the class to be garbage collected.
One of the main advantages over Koin and Kodein is that Toothpick 3 still supports the use of annotations. Just like many other successful Android libraries Toothpick 3 uses code generation to help with injection. Toothpick fully supports incremental annotation processing so it won’t slow down your build.
As Toothpick 3 comes with a new Kotlin friendly API you can use syntax like
by inject() or
by lazy()if you prefer this over annotations. Toothpick 3 also borrows a-lot of nice things(inline modules, binding syntax) from both Koin and Kodein.
The following activity uses a custom annotation to install the dependencies in the activity scope.
As this class is annotated with
@ActivityScope it will be created as a Singleton in the scope above.
Interesting, if we only used the
@Singleton annotation then this class would be available in all scopes until the app is closed. These classes can be considered to be global.
To avoid using
@Inject constructor when defining your classes Toothpick has a nice new handy annotation
@InjectConstructor as shown above.
To avoid opening the scope tree for every activity you can easily add the following to a BaseActivity and get child activities to override the
So, if your activity is installing an inline module it would look like this.
And if your activity is using the
@Inject annotation then it will look like this.
Remember don’t forget to check out my GitHub repo it shows all the common ways how to inject different classes and how you might want to setup Toothpick 3 in your application.
You should also play around with the scopes and try to use dependencies that haven’t been defined in those scopes yet. You should get errors such as
NoFactoryFoundException or no parent scope supports this annotation.
Toothpick 3 provides a lot more than what I have discussed today but what I have shown here should be more than enough to get you started on your own.
Since Toothpick 3 still supports the
@Inject annotation from JSR 330 it would be much easier to migrate from Dagger 2 to Toothpick 3 than either Koin or Kodein. If you want to avoid the complexity and boilerplate of Dagger 2 and make the lives of your developers better then Toothpick 3 is the right option for you.
Remember if you want to clean your teeth and your only tool is a dagger then things could get really messy but if you have the right tool for the job, in this case, a toothpick then things get much simpler. The same holds true for dependency injection.
It’s also great to see that developers are extending their use of Koin and Kodein beyond Android. Dependency injection on the server is now evolving like it did on Android. As we move to serverless technologies such as Lambdas and Cloud Functions, java microservices now need to start up fast and use light and efficient frameworks just like on our resource constraint mobile devices.
Btw, thank you Groupon for providing such an awesome library.
Here are a few notes on the usage of Toothpick 3 in the example above.
- You can install dependencies in the root scope
- The app scope and root scope are essentially the same, the app scope is not required
- If you still want to use the app scope you can also pass the app instance
- The custom scope annotations can be used at any level in the tree