错误:如果没有@Inject构造函数或@Provides注释的方法,则无法提供Dagger / MissingBinding CryptoCurrencyViewModelFactory

时间:2019-02-13 10:53:20

标签: android kotlin dagger-2

我不明白为什么会出现此错误:

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并被注入。

source code

1 个答案:

答案 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注释。