Android + Kotlin + Hilt: Dependency Injection vs Static Methods

  softwareengineering

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.

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

LEAVE A COMMENT