'How make AppModule works in Dagger 2 with kotlin App

I was developing an App in Koltin where I try to use Dagger 2 as Dependecy injector.

For this I make my AppModule.kt file like this:

package com.plcoding.jetpackcomposepokedex.di

import android.content.Context
import com.plcoding.jetpackcomposepokedex.data.local.LocalDatabase
import com.plcoding.jetpackcomposepokedex.data.local.PokemonDAO
import com.plcoding.jetpackcomposepokedex.data.remote.PokeApi
import com.plcoding.jetpackcomposepokedex.repository.PokemonRepository
import com.plcoding.jetpackcomposepokedex.util.Constants.BASE_URL
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import dagger.hilt.android.components.ActivityRetainedComponent
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.scopes.ActivityScoped
import dagger.hilt.components.SingletonComponent
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import javax.inject.Singleton

@Module
@InstallIn(ActivityComponent::class)
object AppModule {

    @ActivityScoped
    @Provides
    fun providePokemonRepository(api: PokeApi) = PokemonRepository(api)

    @ActivityScoped
    @Provides
    fun providePokeApi(): PokeApi
    {
        return Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create())
            .baseUrl(BASE_URL)
            .build()
            .create(PokeApi::class.java)
    }

    @ActivityScoped
    @Provides
    fun provideDB(@ApplicationContext context: Context): LocalDatabase? {
        return LocalDatabase.getInstance(context = context)
    }

    @ActivityScoped
    @Provides
    fun provideDao(localDatabase: LocalDatabase): PokemonDAO {
        return localDatabase.dao()
    }
}

But when I build and rebuild I get the following error:

e: C:\Users\manuel.lucas\AndroidStudioProjects\JetpackComposePokedex\app\build\generated\source\kapt\debug\com\plcoding\jetpackcomposepokedex\PokedexApplication_HiltComponents.java:134: error: [Dagger/IncompatiblyScopedBindings] com.plcoding.jetpackcomposepokedex.PokedexApplication_HiltComponents.ViewModelC scoped with @dagger.hilt.android.scopes.ViewModelScoped may not reference bindings with different scopes:
  public abstract static class SingletonC implements PokedexApplication_GeneratedInjector,
                         ^
      @dagger.hilt.android.scopes.ActivityScoped class com.plcoding.jetpackcomposepokedex.repository.LocalRepository
      @dagger.hilt.android.scopes.ActivityScoped class com.plcoding.jetpackcomposepokedex.repository.PokemonRepository [com.plcoding.jetpackcomposepokedex.PokedexApplication_HiltComponents.SingletonC ? com.plcoding.jetpackcomposepokedex.PokedexApplication_HiltComponents.ActivityRetainedC ? com.plcoding.jetpackcomposepokedex.PokedexApplication_HiltComponents.ViewModelC]

Regarding the viewmodels classes and repositories are the following:

PokemonDetailViewModel::class

@HiltViewModel
class PokemonDetailViewModel @Inject constructor(
    private val repository: PokemonRepository
): ViewModel() {

     suspend fun getPokemonInfo(pokemonName : String):WrapperResponse<Pokemon>{
     return repository.getPokemonInfo(pokemonName)
    }

}

PokemonListViewModel


@HiltViewModel
class PokemonListViewModel @Inject constructor(
    private val repository: PokemonRepository
) : ViewModel() {

    private var curPage = 0
    var pokemonList = mutableStateOf<List<PokedexListEntry>>(listOf())
    var loadError = mutableStateOf("")
    var isLoading = mutableStateOf(false)
    var endReached = mutableStateOf(false)

    private var cachedPokemonList = listOf<PokedexListEntry>()
    private var isSearchStarting = true
    var isSearching = mutableStateOf(false)

    init {
        loadPokemonPaginated()
    }

    fun searchPokemonList(query: String) {
        val listToSearch = if (isSearchStarting) {
            pokemonList.value
        } else {
            cachedPokemonList
        }

        viewModelScope.launch(Dispatchers.Default) {

            if (query.isEmpty()) {
                pokemonList.value = cachedPokemonList
                isSearching.value = false
                isSearchStarting = true
                return@launch
            }

            val results = listToSearch.filter {
                it.pokemonName.contains(query.trim(), ignoreCase = true) ||
                        it.number.toString() == query.trim()
            }

            if (isSearchStarting) {
                cachedPokemonList = pokemonList.value
                isSearchStarting = false
            }

            pokemonList.value = results
            isSearching.value = true

        }
    }

    fun loadPokemonPaginated() {

        viewModelScope.launch {
            isLoading.value = true
            val result = repository.getPokemonList(PAGE_SIZE, offset = curPage * PAGE_SIZE)

            when (result) {
                is WrapperResponse.Sucess -> {
                    endReached.value = curPage * PAGE_SIZE >= result.data!!.count
                    val pokedexEntries = result.data.results.mapIndexed { index, entry ->
                        val number = if (entry.url.endsWith("/")) {
                            entry.url.dropLast(1).takeLastWhile { it.isDigit() }
                        } else {
                            entry.url.takeLastWhile { it.isDigit() }
                        }



                        val url =
                            "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${number}.png"
                        PokedexListEntry(entry.name.replaceFirstChar {
                            if (it.isLowerCase()) it.titlecase(
                                Locale.ROOT
                            ) else it.toString()
                        }, url, number.toInt())
                    }

                    curPage++
                    loadError.value = ""
                    isLoading.value = false
                    pokemonList.value += pokedexEntries
                }
                is WrapperResponse.Error -> {

                    loadError.value = result.message!!
                    isLoading.value = false

                }
            }
        }
    }

    fun calcDominantColor(drawable: Drawable, onFinish: (Color) -> Unit) {
        val bmp = (drawable as BitmapDrawable).bitmap.copy(Bitmap.Config.ARGB_8888, true)
        Palette.from(bmp).generate { palette ->
            palette?.dominantSwatch?.rgb?.let { colorValue ->
                onFinish(Color(colorValue))
            }
        }
    }


And repositories:


@ActivityScoped
class PokemonRepository @Inject constructor(
    private val api: PokeApi
) {

    suspend fun getPokemonList(limit:Int,offset:Int):WrapperResponse<PokemonList>{
        val response = try{
            api.getPokemonList(limit,offset)
        }catch(e:Exception){
            return WrapperResponse.Error("An Unknown error occured.")
        }
      return WrapperResponse.Sucess(response)
    }


    suspend fun getPokemonInfo(pokemonName:String):WrapperResponse<Pokemon>{
        val response = try{
            api.getPokemonInfo(pokemonName)
        }catch(e:Exception){
            return WrapperResponse.Error("An Unknown error occured.")
        }
        return WrapperResponse.Sucess(response)
    }

}

@ActivityScoped
class LocalRepository @Inject constructor(val dao: PokemonDAO) {

    fun insertPokemon(pokemonRoom: PokemonRoom): Long{
        return dao.insertPokemon(pokemonRoom)
    }
    fun selectLocalPokemons(): WrapperResponse<List<RoomResponse>>{

        val response = try{
            dao.selectPokemonBought()
        }catch (ex:Exception){
          return WrapperResponse.Error("Connection to localDatabase failed")
        }

        return WrapperResponse.Sucess(response)
    }

}

I try to change @ActivityComponent for @SingletonComponent, and @ActivityScope for @Singleton but thows the same error.

If you have some more knoledge about dagger and his used, and can help, take thanks in advace !

[EDIT]

I change as follow my AppModule as suggest in comments, changing ActivityScope annotation by Singleton, and change return type of provideDB without questions mark:


@Module
@InstallIn(SingletonComponent::class)
object AppModule {

    @Provides
    @Singleton
    fun providePokemonRepository(api: PokeApi) = PokemonRepository(api)

    @Provides
    @Singleton
    fun providePokeApi(): PokeApi
    {
        return Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create())
            .baseUrl(BASE_URL)
            .build()
            .create(PokeApi::class.java)
    }

    @Provides
    @Singleton
    fun provideDB(@ApplicationContext context: Context): LocalDatabase {
        return LocalDatabase.getInstance(context = context)
    }

    @Provides
    @Singleton
    fun provideDao(localDatabase: LocalDatabase): PokemonDAO {
        return localDatabase.dao()
    }
}

And I every error disapear except binding scope error, due to I need to pass context to provideDb method

error: [Dagger/IncompatiblyScopedBindings] com.plcoding.jetpackcomposepokedex.PokedexApplication_HiltComponents.SingletonC scoped with @Singleton may not reference bindings with different scopes:
  public abstract static class SingletonC implements PokedexApplication_GeneratedInjector,
                         ^
      @org.jetbrains.annotations.NotNull @dagger.hilt.android.scopes.ActivityScoped @Provides com.plcoding.jetpackcomposepokedex.data.local.LocalDatabase com.plcoding.jetpackcomposepokedex.di.AppModule.provideDB(@dagger.hilt.android.qualifiers.ApplicationContext android.content.Context)

error: [Dagger/IncompatiblyScopedBindings] com.plcoding.jetpackcomposepokedex.PokedexApplication_HiltComponents.ViewModelC scoped with @dagger.hilt.android.scopes.ViewModelScoped may not reference bindings with different scopes:
  public abstract static class SingletonC implements PokedexApplication_GeneratedInjector,
                         ^
      @dagger.hilt.android.scopes.ActivityScoped class com.plcoding.jetpackcomposepokedex.repository.LocalRepository [com.plcoding.jetpackcomposepokedex.PokedexApplication_HiltComponents.SingletonC ? com.plcoding.jetpackcomposepokedex.PokedexApplication_HiltComponents.ActivityRetainedC ? com.plcoding.jetpackcomposepokedex.PokedexApplication_HiltComponents.ViewModelC]



[EDIT]

Finally after some searching, I found I need to create an AppComponent which have my differents modules, being one of this, the AplicationContextModule which provide the context. But my question now is, how I do that? Do you know some example which actually works?

I try to do this:

AppComponent.kt


@Singleton
@Component(modules = [
    AppModule::class,
    ApplicationContextModule::class
])
interface AppComponent {

    @Component.Builder
    interface Builder {
        fun build(): AppComponent
        @BindsInstance
        fun application(application: Application): Builder

    }
}

ApplicationContextModule


@Module
@InstallIn(SingletonComponent::class)
class ApplicationContextModule {
    private var context:Context? = null
    constructor(){

    }

    constructor(context:Context){
        this.context = context
    }
    @Provides
    @ApplicationContext
    fun provideContext(): Context {
        return context!!
    }
}

But when I try to instantiate in my Application class I get the following error as cannot access application method:

enter image description here



Solution 1:[1]

try to

  1. add @Singleton Below each @Provides
  2. Your fun provideDB Returns a nullable LocalDatabase, make it non-nullable.

This should solve some (hopefully all) error messages.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Sebastian