0

I've already read this carefully, but still need more clarification.

I'm not new to dependency injection, but new to Hilt, and trying to implement Hilt in my multi-module app.

The reason?

I currently have a static application class wich not only provides a context, but also a singleton SQLite database, and it also holds all app configuration in Kotlin constants, and StackOverflow users said no (about my AppSettings class), this is bad idea because makes your code difficult to test, use DI. OK, that's why I started to learn Hilt.

I'm trying to use best practices, so I first created a multi-module app (to decouple as much as I can), and so I have a "Common" module, "Repository" module, "Core" module, a "Services" module and so and, to give an example, I have a "ExceptionHandler" class in "Common" module which holds static methods (not functions) which log all app exceptions.

Having problems to inject that ExceptionHandler class and use it in MainActivity (for example) some guy in SO said: why are you trying to inject that static methods? Why don't you just use them as ExceptionHandler.logException()?

Well, this is the way I'm currently doing it (the static way), but I'm trying to move to DI in favor of keeping my app decoupled and stay up to date with best practices, but one says "use DI" and the other says "use static methods", why complicating with DI? Your logException methods are not returning a value, so they cannot be considered a real dependency..." and blah blah blah.

With all this I'm really confused on when to use DI and when not, and if my attempts to include Hilt in my app are a good choice.

I will appreciate your clarifications regarding this topic.

Edit: I'll put some of my code.

ExceptionHandler:

object ExceptionHandler {
    fun logException(e: Exception): Boolean {
        try {
            if (isOnline) {
                val strException = formatException(e)
                logException(strException)
            }
        } catch (ignored: Exception) {
        }

        return true
    }

CommonModule class providing ExceptionHandler:

@Module
@InstallIn(SingletonComponent::class)
class CommonModule {

    @Singleton
    @Provides
    fun provideExceptionHandler(): ExceptionHandler {
        return ExceptionHandler
    }
}

MainActivity:

@AndroidEntryPoint
class MainActivity
{
    ...
    @Inject
    lateinit var common: CommonModule
    lateinit var exception: ExceptionHandler
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
    ...
    exception = common.provideExceptionHandler()
    ...
    exception.logException(ex)
    ...
}

Build error:

error: [Dagger/MissingBinding] exception.CommonModule cannot be provided without an @Inject constructor or an @Provides-annotated method.

1 Answer 1

0

The more a class knows about (statically) the less the world can change around it without breaking it. If you want to be pragmatic the next thing to ask is what is likely to change? Is it likely to change when I can't change this class? If we're talking about math.abs() that's not likely to change.

When a class calls a static method it's insisting that the static class must exist and still does whatever it needs. When a class calls a method on an object that it didn't build it doesn't even know what implementation it's calling. That means the change can be managed outside the class. Just pass the class whatever the correct implementation is now. That's dependency injection. You don't need a container to do it.

Be sure you really need this flexibility before you get too invested in a container. While there is a lot of power here there is also enough rope to hang yourself.

Minimize what a class knows about to protect it from likely change. For things that likely wont change, protect the class from over engineering.

DI without a container, that is Pure DI, is more complex than using static methods. But container based DI is complex enough that it becomes something you have to mention when advertising the job working with this code. It's no longer a Kotlin programming job. It's a Kotlin + Hilt programming job. Be sure you're getting enough out of it to justify that.

5
  • What a complete and detailed explanation @candied_orange, Thank you very much for that. I'll have to read it carefully more than once because I'm exhausted today, but I appreciate your time. I'll wait a little to see if more answers arrive before marking it as correct :) Jul 22, 2022 at 16:49
  • BTW @candied_orange, and talking about DI, should I always inject an Interface instead of the actual class? Jul 22, 2022 at 17:13
  • That's worthy of it's own question. Jul 22, 2022 at 17:16
  • I have read your answer again and it is just a great answer @candied_orange. Thanks again for your time, and I'll ask the one regarding interfaces in another question. Jul 22, 2022 at 19:14
  • Given your answer @candied_orange, I'll use my ExceptionHandler (given in the example) as a static class and AppSettings (with context + singleton database + app configuration) as Hilt ApplicationClass. I also guess Repository should be injected, because functions could change, the same for services. I understand that using DI or static classes depend on the context, and can be mixed in a project with no problems, I understand that if you use DI, this doesn't mean all your classes must be injected. One last thing, could you edit my post with a possible solution to the problem I describe.Thanks Jul 22, 2022 at 19:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.