Unintended Behaviour | Unload / loadModules in Hilt

Gergely Hegedüs
4 min readJul 31, 2021

Hilt has been stable for a while, but after burning myself with dagger’s complexity, I was discouraged to start working with it. However when I actually tried it out, I was pleasantly surprised, it is easier to use than I expected.

Until… I wanted to have a Component which is only available for logged in user. The idea is simple, I want to throw away all my memory caches, related to the user, when the user logs out. I remember being able to do that with dagger, even thou it wasn’t the easiest. I found this article, which explains it very well how this can be achieved with Hilt: Hilt — Adding components to the hierarchy. I recommend it, so you have a better understanding of Hilt, it is really awesome.

Koin loadKoinModules

Still, I wasn’t satisfied, seemed like the opinionated Hilt cannot be fit for my use case easily. Creating a component and entries and losing field injection just doesn’t seemed good enough for me. Then I realised how I do this with Koin usually.

With Koin you can easily replace whole modules with the loadKoinModules, unloadKoinModules. That’s what I really want, to be able to drop my classes that contain caching and just replace them with new ones.

Starting point

We have an expected behaviour, but can it really be done with Hilt? After some tinkering and inspections, I came up with the following module structure:

Let’s start with ReloadableLoggedInUserModule , this is just an abstraction, I plan to use this in my LogoutUseCase to reload my module.

As the name suggests ReloadableLoggedInUserModuleImpl is the implementation of this interface. It also provides itself as this interface, so ReloadableLoggedInUserModule can be injected anywhere.

Now let’s see what is happening with that FooRepository . Instead of constructor injection, it is created by this Module, but not a new one necessarily. The module actually keeps reference to the last provided instance and returns that each time it is requested again. This means, it acts like a Singleton, it is created once and returned over and over again.

The fooDependency is injected by wrapping it in a Provider, that’s because this way we keep its scope indifferent of our implementation.

And the last piece, the actual implementation of the reload function, we simply clear the fooRepository , which means the next time it is injected somewhere, a new instance is created and then kept.

Works, but that’s no better

This clearly works as I want it to be, but I wouldn’t want to write this provide function for every object I want to clear. Also I lost the constructor injection, so this is not better than how we could do this with just Hilt.

Unless … giving it a closer look, it really looks like something that’s the right kind of boilerplate, the kind that can be generated via annotation processing.

What we need is to replace the Inject annotation on the FooRepository’s constructor with our own. That way, based on this new annotation, all the Provides functions in our module can be generated.

I won’t bore you with the details of that, in short however: when creating an annotation processor, all I’m doing is create a test case, create a couple of files that are expected input, and create a few that are expected outputs. I use https://github.com/tschuchortdev/kotlin-compile-testing to be able to test my annotation processors.
That way I know exactly when I’m finished and comparing my actual generated code with the expected one, I always know what still needs to be adjusted.

Pitfall

I made one mistake, I didn’t verify that my generated kotlin code does even interact with Hilt. Well, it didn’t. Why? I am not completely sure. From what I gathered, kapt doesn’t do multiple passes on generated kotlin source files. This means it won’t be used as an input for Hilt annotation processing, unlike generated Java sources which would be used. So the solution is simple, just switch from KotlinPoet to JavaPoet. Not ideal, but since we got so far…

Conclusions

So with this new annotation processor and with minimal boilerplate, I can actually achieve the desired behaviour.

Anyway, is it really worth using? I am not sure, it works fine for my project, but I am unsure if it would completely fit your project as well. For me this was more about solving my problem with minimal compromise while understanding Hilt a bit more in detail. I still prefer Koin, so I don’t think I personally going to use Hilt in the long run.

Still, I hope you may take away something from this process.

Repository

If you wish to check out how exactly it works or how code generation happens, you may refer to the GitHub page. You may also play around with it, since I released a package for it.

Now go code ;)

--

--