我不明白为什么会出现此错误:
C:\Users\xxx\Documents\xxx\workspace\android\hellomvvmdagger2kotlin\app\build\tmp\kapt3\stubs\debug\com\xxx\hello_mvvm_dagger2_kotlin\di\component\ApplicationComponent.java:8: error: [Dagger/MissingBinding] [dagger.android.AndroidInjector.inject(T)] com.xxx.hello_mvvm_dagger2_kotlin.viewmodel.CryptoCurrencyViewModelFactory cannot be provided without an @Inject constructor or an @Provides-annotated method.
public abstract interface ApplicationComponent {
^
com.xxx.hello_mvvm_dagger2_kotlin.viewmodel.CryptoCurrencyViewModelFactory is injected at
com.xxx.hello_mvvm_dagger2_kotlin.di.module.ViewModelModule.bindViewModelFactory(factory)
androidx.lifecycle.ViewModelProvider.Factory is injected at
com.xxx.hello_mvvm_dagger2_kotlin.ui.CryptoCurrencyActivity.cryptoCurrencyViewModelFactory
com.xxx.hello_mvvm_dagger2_kotlin.ui.CryptoCurrencyActivity is injected at
dagger.android.AndroidInjector.inject(T)
component path: com.xxx.hello_mvvm_dagger2_kotlin.di.component.ApplicationComponent ? com.xxx.hello_mvvm_dagger2_kotlin.di.module.ActivityModule_ContributeCryptoCurrenciesActivity.CryptoCurrencyActivitySubcomponent
我尝试遵循此link来实现MVVM-Dagger-Kotlin。
成绩(模块)
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
applicationId "com.xxx.hello_mvvm_dagger2_kotlin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.compileSdkVersion
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
}
}
}
buildTypes {
debug {
buildConfigField 'String', 'URL', '"https://api.coinmarketcap.com/v1/"'
}
release {
buildConfigField 'String', 'URL', '"https://api.coinmarketcap.com/v1/"'
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "androidx.appcompat:appcompat:${rootProject.ext.appcompatVersion}"
implementation "androidx.constraintlayout:constraintlayout:${rootProject.ext.constraintLayoutVersion}"
// moshi
implementation "com.squareup.moshi:moshi-kotlin:${rootProject.ext.moshiKotlinVersion}"
// dagger2
implementation "com.google.dagger:dagger:${rootProject.ext.dagger2Version}"
implementation "com.google.dagger:dagger-android:${rootProject.ext.dagger2Version}"
implementation "com.google.dagger:dagger-android-support:${rootProject.ext.dagger2Version}"
kapt "com.google.dagger:dagger-compiler:${rootProject.ext.dagger2Version}"
kapt "com.google.dagger:dagger-android-processor:${rootProject.ext.dagger2Version}"
// room
implementation "android.arch.persistence.room:runtime:${rootProject.ext.archRoomVersion}"
implementation "android.arch.persistence.room:rxjava2:${rootProject.ext.archRoomVersion}"
kapt "android.arch.persistence.room:compiler:${rootProject.ext.archRoomVersion}"
// lifecycle
implementation "android.arch.lifecycle:extensions:${rootProject.ext.archLifecycleVersion}"
implementation "android.arch.lifecycle:extensions:${rootProject.ext.archLifecycleVersion}"
kapt "android.arch.lifecycle:compiler:${rootProject.ext.archLifecycleVersion}"
// retrofit
implementation "com.squareup.retrofit2:retrofit:${rootProject.ext.retrofitVersion}"
implementation "com.squareup.retrofit2:converter-moshi:${rootProject.ext.moshiConverterVersion}"
implementation "com.squareup.retrofit2:adapter-rxjava2:${rootProject.ext.rxJavaAdapterVersion}"
// rxJava rxAndroid
implementation "io.reactivex.rxjava2:rxjava:${rootProject.ext.rxJava2Version}"
implementation "io.reactivex.rxjava2:rxandroid:${rootProject.ext.rxAndroidVersion}"
// test
testImplementation "junit:junit:${rootProject.ext.junitVersion}"
androidTestImplementation "androidx.test:runner:${rootProject.ext.testRunnerVersion}"
androidTestImplementation "androidx.test.espresso:espresso-core:${rootProject.ext.testEspressoVersion}"
}
成绩(项目)
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.3.20'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
ext {
//app
compileSdkVersion = 28
minSdkVersion = 16
targetSdkVersion = 28
appcompatVersion = "1.1.0-alpha01"
constraintLayoutVersion = "2.0.0-alpha3"
moshiKotlinVersion = "1.5.0"
dagger2Version = "2.16"
archRoomVersion = "1.0.0"
archLifecycleVersion = "1.1.0"
retrofitVersion = "2.3.0"
moshiConverterVersion = "2.3.0"
rxJavaAdapterVersion = "2.3.0"
rxAndroidVersion = "2.1.0"
rxJava2Version = "2.1.0"
//test
junitVersion = "4.12"
//testImpl
testRunnerVersion = "1.1.1"
testEspressoVersion = "3.1.1"
}
task clean(type: Delete) {
delete rootProject.buildDir
}
CryptoCurrencyApplication
class CryptoCurrencyApplication: Application(), HasActivityInjector {
@Inject lateinit var activityInjector: DispatchingAndroidInjector<Activity>
override fun onCreate() {
super.onCreate()
DaggerApplicationComponent.builder()
.application(this)
.build()
.injectLo(this)
}
override fun activityInjector() = activityInjector
}
ApplicationComponent
@Singleton
@Component(
modules = [
AndroidInjectionModule::class,
ApplicationModule::class,
DatabaseModule::class,
NetworkModule::class,
RepositoryModule::class,
ActivityModule::class
])
interface ApplicationComponent {
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: Application): Builder
fun build(): ApplicationComponent
}
fun injectLo(application: CryptoCurrencyApplication)
}
ViewModelModule
@Module
abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(CryptoCurrencyViewModel::class)
abstract fun bindCryptoCurrencyViewModel(cryptoCurrencyViewModel: CryptoCurrencyViewModel): ViewModel
@Binds
abstract fun bindViewModelFactory(factory: CryptoCurrencyViewModelFactory) : ViewModelProvider.Factory
}
ViewModelKey
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER)
@MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
CryptoCurrencyViewModel
class CryptoCurrencyViewModel @Inject constructor(private val cryptoCurrencyRepository: CryptoCurrencyRepository): ViewModel() {
var result: MutableLiveData<List<CryptoCurrency>> = MutableLiveData()
var error: MutableLiveData<String> = MutableLiveData()
var loader: MutableLiveData<Boolean> = MutableLiveData()
private lateinit var disposableObserver: DisposableObserver<List<CryptoCurrency>>
fun getResult(): LiveData<List<CryptoCurrency>> = result
fun getError(): LiveData<String> = error
fun getLoader(): LiveData<Boolean> = loader
fun loadCryptoCurrencies(limit: Int, offset: Int) {
disposableObserver = object : DisposableObserver<List<CryptoCurrency>>() {
override fun onComplete() {
}
override fun onNext(cryptoCurrencies: List<CryptoCurrency>) {
result.postValue(cryptoCurrencies)
loader.postValue(false)
}
override fun onError(e: Throwable) {
error.postValue(e.message)
loader.postValue(false)
}
}
cryptoCurrencyRepository.getCryptoCurrencies(limit, offset)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.debounce(400, TimeUnit.MILLISECONDS)
.subscribe(disposableObserver)
}
fun disposeElements() {
disposableObserver?.let {
if (it.isDisposed) disposableObserver.dispose()
}
}
}
CryptoCurrencyViewModelFactory
@Singleton
class CryptoCurrencyViewModelFactory @Inject constructor(
private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>)
: ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
var creator: Provider<out ViewModel>? = creators[modelClass]
if (creator == null) {
for ((key, value) in creators) {
if (modelClass.isAssignableFrom(key)) {
creator = value
break
}
}
}
if (creator == null) {
throw IllegalArgumentException("unknown model class " + modelClass)
}
try {
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
CryptoCurrencyActivity
class CryptoCurrencyActivity: AppCompatActivity() {
@Inject lateinit var cryptoCurrencyViewModelFactory: CryptoCurrencyViewModelFactory
private var cryptoCurrencyAdapter = CryptoCurrencyAdapter(ArrayList())
private lateinit var cryptoCurrencyViewModel: CryptoCurrencyViewModel
private var currentPage = 0
companion object {
private val TAG = CryptoCurrencyActivity::class.simpleName
}
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_cryptocurrency)
cryptoCurrencyViewModel = ViewModelProviders.of(this, cryptoCurrencyViewModelFactory)
.get(CryptoCurrencyViewModel::class.java)
}
}
如果有人可以帮助我,我已经在代码中发现错误上浪费了很多时间。我希望 ViewModelFactory 不为null并被注入。
答案 0 :(得分:0)
在您的CryptoCurrencyViewModelFactory
构造函数中,将private val creators: Map<Class<out ViewModel>
更改为private val creators: MutableMap<Class<out ViewModel>
。 Kotlin的Map
强制采用不变性,但Dagger内部需要使地图可变。
编辑:
在检查了源代码之后,我注意到您正在kotlin.reflect.jvm.internal.impl.javax.inject.Inject
类中导入CryptoCurrencyViewModelFactory
。将其更改为正确的导入(javax.inject.Inject
),否则Dagger无法识别@Inject
注释。