Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Provide function dependency using Dagger 2

I would like to provide a function as dependency using Dagger 2:

@Module
class DatabaseModule {

    @Provides
    @Singleton
    fun provideDatabase(application: Application, betaFilter: (BetaFilterable) -> Boolean): Database {
        return Database(application, BuildConfig.VERSION_CODE, betaFilter)
    }

    @Provides
    @Suppress("ConstantConditionIf")
    fun provideBetaFiler(): (BetaFilterable) -> Boolean {
        return if (BuildConfig.FLAVOR_audience == "regular") {
            { it.betaOnly.not() }
        } else {
            { true }
        }
    }

}

Unfortunately, it does not seem to work:

[dagger.android.AndroidInjector.inject(T)] kotlin.jvm.functions.Function1<? 
super com.app.data.BetaFilterable,java.lang.Boolean> 
cannot be provided without an @Provides-annotated method.

What am I missing here?

like image 760
Benjamin Avatar asked Jul 18 '18 15:07

Benjamin


People also ask

What is Dagger 2 used for?

Dagger is arguably the most used Dependency Injection, or DI, framework for Android. Many Android projects use Dagger to simplify building and providing dependencies across the app. It gives you the ability to create specific scopes, modules, and components, where each forms a piece of a puzzle: The dependency graph.

What does @singleton annotation do?

The @Singleton annotation is used to declare to Dagger that the provided object is to be only initialized only once during the entire lifecycle of the Component which uses that Module.

How do you inject value at runtime in Dagger?

Inject values at runtime with UI in Dagger2:pureMathModule("Book Name") . build() . inject(this); The difference between DaggerComponent create() and in build() is - create() works when no runtime argument is passed into the constructor, else we use build() method.


2 Answers

Yes, this can be done in Kotlin.

You need to add @JvmSuppressWildcards at the injection site to ensure the signature matches. (source)

I wrote the following to verify it:

import dagger.Component
import dagger.Module
import dagger.Provides
import javax.inject.Singleton

class G constructor(val function: Function1<Int, Boolean>)

@Singleton
@Module
class ModuleB {
    @Provides
    fun intToBoolean(): (Int) -> Boolean {
        return { it == 2 }
    }
    @JvmSuppressWildcards
    @Provides fun g(intToBoolean: (Int) -> Boolean): G {
        return G(intToBoolean)
    }
}

@Singleton
@Component(modules = [ModuleB::class])
interface ComponentB {
    fun g(): G
}

val componentB = DaggerComponentB.create()
val g = componentB.g()
println(g.function(2)) // true
println(g.function(3)) // false

Background: Examining @Kiskae's response, it seems the problem is that a parameter of function type in Kotlin becomes contravariant on its own parameter types when the code is converted to Java bytecode. If this doesn't make sense to you, don't worry. It's not necessary to understand it to use the technique I show above.

like image 77
Julian A. Avatar answered Sep 23 '22 19:09

Julian A.


As pointed out in Kiskae's answer, it is not possible to make this work using kotlin. You can make it work using java though:

@Module
public class DatabaseModuleJava {

    @Provides
    @Singleton
    public Database provideDatabase(Application application, Function1<BetaFilterable, Boolean> betaFilter) {
        return new Database(application, BuildConfig.VERSION_CODE, betaFilter);
    }

    @Provides
    @Singleton
    @SuppressWarnings("ConstantConditions")
    public Function1<BetaFilterable, Boolean> provideBetaFiler() {
        if (BuildConfig.FLAVOR_audience.equals("regular")) {
            return betaFilterable -> !betaFilterable.getBetaOnly();
        } else {
            return betaFilterable -> true;
        }
    }

}
like image 39
Benjamin Avatar answered Sep 21 '22 19:09

Benjamin