如何使用 kotlin App 在 Dagger 2 中制作 AppModule
我正在 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:
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
尝试
@Provides
下面添加@Singleton
fun ProvideDB
返回一个可为空的LocalDatabase
,使其不可为空。这应该可以解决一些(希望是全部)错误消息。
try to
@Singleton
Below each@Provides
fun provideDB
Returns a nullableLocalDatabase
, make it non-nullable.This should solve some (hopefully all) error messages.