eqtèöck
eqtèöck

Reputation: 1075

Dagger2 creating a map of concrete instances fails with "cannot be provided without an @Provides-annotate"

I am playing around with Dagger collection, in particular with map.

I want to use Dagger2 to inject a map whose key is an enum and the values a concrete implementation of an interface. The map is injected in a presenter. An unique component instantiates the presenter, an activity uses the instantiation to display the strings produced by the value of the map.

I get the following error:

e: ../DaggerMap/app/build/tmp/kapt3/stubs/debug/com/aklal/briquedagger2/MainComponent.java:7: error: [Dagger/MissingBinding] java.util.Map<com.aklal.briquedagger2.Lapse.TIME,? extends com.aklal.briquedagger2.Lapse.ChristmasTime> cannot be provided without an @Provides-annotated method.
public abstract interface MainComponent {
                ^
      java.util.Map<com.aklal.briquedagger2.Lapse.TIME,? extends com.aklal.briquedagger2.Lapse.ChristmasTime> is injected at
          com.aklal.briquedagger2.MainPresenter(mapOfLapse)
      com.aklal.briquedagger2.MainPresenter is injected at
          com.aklal.briquedagger2.MainModule.getMainPresenter(connection)
      com.aklal.briquedagger2.Presenter is provided at
          com.aklal.briquedagger2.MainComponent.presenter()

FAILURE: Build failed with an exception.

The "project" can be found here

Implementation

I have an interface ChristmasTime which is implemented by two classes:

These implementation are similarly simply defined as follow:

class TimeSinceLastChristmas @Inject constructor(): ChristmasTime {
    override fun getLapseOfTime() = "SINCE TEST"
}    

I want to let Dagger2 create a map with

The key value is defined as follow:

@MapKey
annotation class TimeKey(val value: TIME)

enum class TIME {
    UNTIL,
    SINCE
}

I created a module to provide concrete implementations of type ChristmasTime:

@Module
interface LapseOfTimeModule {
    @Binds
    @IntoMap
    @TimeKey(TIME.UNTIL)
    fun provideLapsesOfTimeUntil(t: TimeUntilNextChristmas): ChristmasTime

    @Binds
    @IntoMap
    @TimeKey(TIME.SINCE)
    fun provideLapsesOfTimeSince(t: TimeSinceLastChristmas): ChristmasTime
}

I want to displayed the string returned by the concrete implementations on the screen. To do so, a presenter communicates with an activity the strings contained in the map (that has been injected in the presenter):

class MainPresenter @Inject constructor(
    private val mapOfLapse: Map<TIME, ChristmasTime>
) : Presenter {
    override fun getDateUntil(): String = mapOfLapse[TIME.UNTIL]?.getLapseOfTime() ?: "UNTIL FAILED"
}

The component to instantiate the presenter in the MainActivity takes the module that defines the map (LapseOfTimeModule) and a MainModule

@Component(modules = [MainModule::class, LapseOfTimeModule::class])
interface MainComponent {
    fun presenter(): Presenter
}

MainModule is:

@Module
interface MainModule {
    @Binds
    fun getMainPresenter(connection: MainPresenter): Presenter
}

And the MainActivity is:

class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var presenter: Presenter


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        presenter = DaggerMainComponent.create().presenter()

        bttDisplayNewDate.setOnClickListener{
            displayDate(
                presenter.getDateSince(),
                presenter.getDateUntil()
            )
        }
    }

    fun displayDate(since: String, until: String) {
        tvSince.text = since
        tvUntil.text = until
    }
}

Does anyone know how to fix that ?

Here are some threads that I read but they did not help much:

Thanks in advance!!

ps: The version of dagger used is 2.24 and kotlin version is 1.3.41

Upvotes: 2

Views: 802

Answers (1)

user2802230
user2802230

Reputation: 11

It's a little bit tricky how it works. In Java it will work. Kotlin decompile Map<K,V> just to Map, and Dagger can't find a Map without types of K and V. To fix it please use just java.util.Map for autogenerated daggers class.

class MainPresenter @Inject constructor(
    private val mapOfLapse: java.util.Map<TIME, ChristmasTime>
) : Presenter {

of course, then you need to map it into kotlin map to have all extension functions.

Upvotes: 0

Related Questions