如何使用 kotlin App 在 Dagger 2 中制作 AppModule

发布于 2025-01-17 03:39:22 字数 11393 浏览 4 评论 0原文

我正在 Koltin 中开发一个应用程序,我尝试使用 Dagger 2 作为 Dependecy 注入器。

为此,我像这样制作 AppModule.kt 文件:

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()
    }
}

但是当我构建和重建时,出现以下错误:

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]

关于 viewmodels 类和存储库如下:

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))
            }
        }
    }


和存储库:


@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)
    }

}

我尝试将 @ActivityComponent 更改为 @ SingletonComponent 和 @Singleton 的 @ActivityScope 但会出现相同的错误。

如果您对匕首及其使用有更多了解,并且可以提供帮助,请提前致谢!

[编辑]

我按照评论中的建议更改我的AppModule,通过单例更改ActivityScope注释,并更改provideDB的返回类型而不带问号:


@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()
    }
}

并且除了绑定范围错误之外,每个错误都消失了,因为我需要将上下文传递给provideDb方法

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]



[编辑]

最后经过一些搜索,我发现我需要创建一个具有不同模块的 AppComponent,其中之一是提供上下文的 AplicationContextModule。但我现在的问题是,我该怎么做?你知道一些实际有效的例子吗?

我尝试这样做:

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!!
    }
}

但是当我尝试在我的应用程序类中实例化时,我收到以下错误,因为无法访问应用程序方法:

在此处输入图像描述

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

并安 2025-01-24 03:39:22

尝试

  1. 在每个@Provides下面添加@Singleton
  2. 您的fun ProvideDB返回一个可为空的LocalDatabase,使其不可为空。

这应该可以解决一些(希望是全部)错误消息。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文