简体   繁体   中英

Android + Kotlin + Hilt: Injecting into static methods classes

I'm not very clear about the best way to inject into a static methods helper class (lets say a Custom class).

I'm kinda new to Kotlin, and as I've learnt we can access a method statically in two ways:

  1. Object class.
  2. Class + companion object.

To start, I'm not sure which one is the most recommended one (if there is a best practice regarding this), but my "problem" arises when needing to inject dependencies into a static method class.

Let's go with a simple example:

I have a static methods class called AWUtils (not decided if it should be an object class or a class with companion object yet though, and this will most likely depend on the injection mechanism recommended) with the next method:

fun setAmpersand2Yellow(text2Replace: String, target: String): String {
    return text2Replace.replace(
        target, "<span style=\"color:" +
                app.drawerFooterColor + ";\">" + target + "</span>"
    )
}

Here, app is the instance of my AppSettings class which holds all app configuration so, as you see setAmpersand2Yellow needs AppSettings, and of course I would't pass it as a parameter by any means, so it's a AWUtils dependence.

Using AWUtils as a class with companion object for the static methods I cannot inject directly AppSettings into company object as far as I know (at least I cannot do constructor injection, let me know if I'm wrong) and if I inject into companion object parent class (AWUtils) constructor then I don't know how to access those dependences from the companion object itself (the child).

If I use fields injection in AWUtils as a class then it complains than lateinit field has not been initialised and I don't know how to deal with this, because as far as I know lateinit fields are initialised in onCreate, which does not exist in this kind of classes.

One other possibility is to use an object with fields and set the dependencies values from caller in a static way before calling the method, for example:

object AWUtils {

    var app: AppSettings? = null

    fun setAmpersand2Yellow(text2Replace: String, target: String): String {
        return text2Replace.replace(
            target, "<span style=\"color:" +
                    app.drawerFooterColor + ";\">" + target + "</span>"
        )
    }
}

@AndroidEntryPoint
class OtherClass 
@Inject constructor(private val app: AppSettings) {
    
    fun AnyFunction() {
        var mystr = "whatever"
        AWUtils.app = app
        var yellowStr = AWUtils.setAmpersand2Yellow(myStr)
    }
}

In the end, I'm not sure on how to supply dependencies to a static methods class and which form of "static" class should I choose.

Edit 1:

Apart from my ApSettings class, I need a context, like for example in this next isTablet method:

val isTablet: String
    get() {
        return ((context.resources.configuration.screenLayout
                and Configuration.SCREENLAYOUT_SIZE_MASK)
                >= Configuration.SCREENLAYOUT_SIZE_LARGE)
    }

In the end, I need a context and my AppSettings (or any other custom classes) to be injected anyway in a class with static methods.

Edit 2:

I could do (from the activity):

AWUtils.context = this
AWUtils.app = app
var isTablet = AWUtils.isTablet

And it works, but rather to be in the need of assigning a value to two fields (or more) every time I need to call a static method, I would prefer the fields to be injected in any way.

That's what dependency injection is meant for, isn't it?

Edit 3: I'm starting to be fed up with Hilt, what is supposed would have been created to simplify our life, only makes our programming life much more complicated.

As you clarified in the comments, you want to have your utils class accessible in an easy way across your codebase, so this answer will focus on that and on your original questions.

I'm kinda new to Kotlin, and as I've learnt we can access a method statically in two ways: Object class or Class + companion object.

Kotlin does not have Java-style statics. One reasoning behind it was to encourage more maintainable coding practices. Static methods and static classes are also a nightmare for testing your code.

In Kotlin you would go with an object (but a class + companion object would work in the same way)

object AWUtils {
    lateinit var appContext: Context
    lateinit var appSettings: AppSettings

    fun initialize(
        appContext: Context,
        appSettings: AppSettings,
        // more dependencies go here
    ) {
        this.appContext = appContext
        this.appSettings = appSettings
        // and initialize them here
    }

    val isTablet: Boolean
        get() = ((appContext.resources.configuration.screenLayout
                and Configuration.SCREENLAYOUT_SIZE_MASK)
                >= Configuration.SCREENLAYOUT_SIZE_LARGE)


    fun setAmpersand2Yellow(text2Replace: String, target: String): String {
        return text2Replace.replace(
            target, "<span style=\"color:" +
                    appSettings.drawerFooterColor + ";\">" + target + "</span>"
        )
    }
}

Since this object should be accessible across the whole application it should be initialized as soon as possible, so in Application.onCreate

@HiltAndroidApp
class Application : android.app.Application() {
    // you can inject other application-wide dependencies here
    // @Inject
    // lateinit var someOtherDependency: SomeOtherDependency

    override fun onCreate() {
        super.onCreate()

        // initialize the utils singleton object with dependencies
        AWUtils.initialize(applicationContext, AppSettings())
    }

Now anywhere in your app code you can use AWUtils and AppSettings

class OtherClass { // no need to inject AppSettings anymore
    
    fun anyFunction() {
        val mystr = "whatever"
        val yellowStr = AWUtils.setAmpersand2Yellow(myStr)

        // This also works
        if (AWUtils.isTablet) {
            // and this as well
            val color = AWUtils.appSettings.drawerFooterColor
        }        
    }
}

There is another way in Kotlin to write helper/util functions, calledextension functions .

Your isTablet check might be written as an extension function like this

// This isTablet() can be called on any Configuration instance
// The this. part can also be omitted
fun Configuration.isTablet() = ((this.screenLayout
        and Configuration.SCREENLAYOUT_SIZE_MASK)
        >= Configuration.SCREENLAYOUT_SIZE_LARGE)

// This isTablet() can be called on any Resources instance 
fun Resources.isTablet() = configuration.isTablet()

// This isTablet() can be called on any Context instance
fun Context.isTablet() = resources.isTablet()

With the above extension functions in place the implementation inside AWUtils would be simplified to

    val isTablet: Boolean
        get() = appContext.isTablet()

Inside (or on a reference of) any class that implements Context , such as Application , Activity , Service etc., you can then simply call isTablet()

class SomeActivity : Activity() {
    fun someFunction() {
        if (isTablet()) {
            // ...
        }
    }
}

And elsewhere where Context or Resources are available in some way, you can simply call resources.isTablet()

class SomeFragment : Fragment() {
    fun someFunction() {
        if (resources.isTablet()) {
            // ...
        }
    }
}

Edit 3: I'm starting to be fed up with Hilt, what is supposed would have been created to simplify our life, only makes our programming life much more complicated.

Yeah, Hilt is focusing on constructor injection and can only do field injection out-of-the-box in very limited cases, afaik only inside Android classes annotated with @AndroidEntryPoint and inside the class extending the Application class when annotated with @HiltAndroidApp .

Docs for @AndroidEntryPoint say

Marks an Android component class to be setup for injection with the standard Hilt Dagger Android components. Currently, this supports activities, fragments, views, services, and broadcast receivers.

If you feel that you need a lot of field injection, because you are working with "static"-like objects in Kotlin, consider using Koin instead of Hilt for your next project.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM