paint-brush
Effortless Assisted Injection in Multi-Module Android Projects: Introducing Anvil Utilsby@ilyagulya
6,943 reads
6,943 reads

Effortless Assisted Injection in Multi-Module Android Projects: Introducing Anvil Utils

by Ilya GulyaMay 8th, 2024
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Anvil is a Kotlin compiler plugin which helps to drastically reduce the boilerplate needed to use Dagger 2 in your application. Also, if properly configured, it can improve build times in your application by removing the requirement to run Dagger 2 annotation processor in your feature modules.
featured image - Effortless Assisted Injection in Multi-Module Android Projects: Introducing Anvil Utils
Ilya Gulya HackerNoon profile picture

Hey there, fellow Android developers! 👋


Are you tired of the hassle that comes with managing assisted injection in your modular app? Well, buckle up because I've got some exciting news for you! 🚀


Introducing Anvil Utils - a game-changing library that will make your life easier when working with Dagger and Anvil in multi-module projects.

The Struggle Is Real

Picture this: you have a beautifully architected modular app with api and implementation modules per feature. Everything is going smoothly until you encounter a situation where your feature contains a factory for an entity and want to expose it in an api, and you need to make this factory available within the Dagger graph. 😰


Let's walk through the ways to do that.

Bare Dagger Approach

For instance, you have such public API for your feature module:

interface MyClass {
	interface Factory {
	    fun create(param1: String, param2: Int): MyClass
	}
}


Traditionally, you'd have to manually create an @AssistedFactory in the implementation module, inherit from the public interface in the API module, and write a Dagger module which binds assisted factory to it's public interface. Also, you would need to maintain both factory interfaces - public one and implementation one. Here's an example:

class DefaultMyClass @AssistedInject constructor(
    @Assisted param1: String,
    @Assisted param2: Int
) : MyClass {
	@AssistedFactory
	interface Factory: MyClass.Factory {
		override fun create(param1: String, param2: Int): DefaultMyClass
	}
}

@Module
interface MyClassFactoryBindingModule {
	@Binds
	fun bindFactory(impl: DefaultMyClass.Factory): MyClass.Factory
}


Seems like a lot of work, doesn't it?

Let's make it easier to maintain step by step

Introducing Anvil

Anvil is a Kotlin compiler plugin which helps to drastically reduce the boilerplate needed to use Dagger 2 in your application.


Also, if properly configured, it can improve build times in your application by removing the requirement to run Dagger 2 annotation processor in your feature modules.


I will not dive deep into what Anvil is and why you should consider using it. That's a topic for another day. Please read through the Anvil readme here to get what it is about. https://github.com/square/anvil


Thanks to Anvil, you can get rid of the binding module using @ContributesBinding:

class DefaultMyClass @AssistedInject constructor(
    @Assisted param1: String,
    @Assisted param2: Int
) : MyClass {
	@ContributesBinding(AppGraph::class, MyClass.Factory::class)
	@AssistedFactory
	interface Factory: MyClass.Factory {
		override fun create(param1: String, param2: Int): DefaultMyClass
	}
}

Anvil will generate corresponding module which will bind DefaultMyClass.Factory to MyClass.Factory for you.


This lowers the amount of boilerplate, but it's still there!


You have to manually keep public factory interface and implementation interface aligned with the constructor! Can we go further and reduce the amount of work needed to support this use case? Sure!

@ContributesAssistedFactory to the Rescue!

Fear not, my friend! With Anvil Utils, you can say goodbye to the manual labor of creating assisted factories. The @ContributesAssistedFactory annotation is here to save the day! 🦸‍♂️


This powerful annotation automatically generates assisted factories for your annotated classes and contributes them to the specified scope as bindings of the provided factory type. It's like having a personal assistant that takes care of all the heavy lifting for you!


You now can remove the @AssistedFactory interface completely and annotate the DefaultMyClass with @ContributesAssistedFactory:

@ContributesAssistedFactory(AppGraph::class, MyClass.Factory::class)
class DefaultMyClass @AssistedInject constructor(
    @Assisted param1: String,
    @Assisted param2: Int
) : MyClass

And voila! Anvil Utils will generate the necessary assisted factory for you, without breaking a sweat. 😎

In the Wild Example

To demonstrate the power of this library, I've made a huge pull request to beautiful open-source project - companion app for the Flipper - Tamagochi for hackers. Please, take a look if you're interested: https://github.com/flipperdevices/Flipper-Android-App/pull/79

Embrace the Power of Anvil Utils


So, what are you waiting for? Give Anvil Utils a try in your next modular Android project and experience the joy of hassle-free assisted injection. Your codebase will thank you, and your fellow developers will be in awe of your newfound superpowers! 💪


Happy coding, and may the power of Anvil Utils be with you! 🚀


P.S. Don't hesitate to open feature requests - I'm eager to improve the library and add more features.