Кинжал не может вводить членов ViewModel - PullRequest
0 голосов
/ 02 июля 2019

Я реализовал пользовательский ViewModelFactory и Dagger DI, чтобы члены ViewModel вводили Dagger автоматически, весь код ниже:

class ViewModelFactory @Inject constructor(
    private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
): ViewModelProvider.Factory {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        val creator = creators[modelClass] ?: creators.entries.firstOrNull {
            modelClass.isAssignableFrom(it.key)
        }?.value ?: throw IllegalArgumentException("unknown model class $modelClass")

        try {
            @Suppress("UNCHECKED_CAST")
            return creator.get() as T
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
    }
}

DI:

@Suppress("unused")
@Module
abstract class ViewModelModule {

    @Binds
    abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}

@Singleton
@Component(
    modules = [
        AndroidInjectionModule::class,
        AppModule::class,
        ViewModelModule::class,
        ActivityModule::class
    ]
)
interface AppComponent: AndroidInjector<App> {

    @Component.Builder
    abstract class Builder: AndroidInjector.Builder<App>()
}

@Suppress("unused")
@Module
abstract class ActivityModule {
    @ContributesAndroidInjector(
        modules = [LoginFragmentBuilderModule::class, LoginViewModelModule::class]
    )
    abstract fun contributeLoginActivity(): LoginActivity

    @ContributesAndroidInjector(modules = [])
    abstract fun contributeMainActivity(): MainActivity
}

@Suppress("unused")
@Module
abstract class LoginViewModelModule {

    @Binds
    @IntoMap
    @ViewModelKey(AccountViewModel::class)
    abstract fun bindAccountViewModel(accountViewModel: AccountViewModel): ViewModel
}

@Suppress("unused")
@Module
abstract class LoginFragmentBuilderModule {

    @ContributesAndroidInjector
    abstract fun contributeAccountFragment(): AccountFragment
}

@Module
object AppModule {

    @JvmStatic
    @Provides
    fun provideGitHubApi(retrofit: Retrofit): IGitHubApi {
        return retrofit.create(IGitHubApi::class.java)
    }

    // A lot of other providers
}

AccountViewModel: :

class AccountViewModel(app: Application) : AndroidViewModel(app) {

    @Inject
    lateinit var gitHubApi: IGitHubApi
}

Я написал его с помощью учебника , но у меня возникает ошибка во время компиляции:

e: /test/di/AppComponent.java:8: error: [Dagger/MissingBinding] [dagger.android.AndroidInjector.inject(T)] test.ui.login.models.AccountViewModel cannot be provided without an @Inject constructor or an @Provides-annotated method. This type supports members injection but cannot be implicitly provided.
public abstract interface AppComponent extends dagger.android.AndroidInjector<com.contedevel.juke.App> {
                ^
      test.ui.login.models.AccountViewModel is injected at
          test.di.LoginViewModelModule.bindAccountViewModel(accountViewModel)

      java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
          test.di.ViewModelFactory.<init>(creators)
      test.di.ViewModelFactory is injected at
          test.di.ViewModelModule.bindViewModelFactory(factory)
      androidx.lifecycle.ViewModelProvider.Factory is injected at
          test.ui.login.AccountFragment.viewModelFactory
      test.ui.login.AccountFragment is injected at
          dagger.android.AndroidInjector.inject(T)
  component path: test.di.AppComponent → test.di.ActivityModule_ContributeLoginActivity.LoginActivitySubcomponent → test.di.LoginFragmentBuilderModule_ContributeAccountFragment.AccountFragmentSubcomponent

Ответы [ 2 ]

1 голос
/ 03 июля 2019

Во-первых, как Dagger будет работать в этом сценарии. Вы связываете экземпляр AccountViewModel через параметр в bindAccountViewModel. Однако теперь кинжал создаст экземпляр AccountViewModel и будет привязан к карте с ключом AccountViewModel::class.

В соответствии с трассировкой стека, Даггер предложил вам использовать инжектор конструктора или предоставить ViewModel, используя метод Provides в модуле. Из нашей текущей реализации второе предложение становится распространенным, так как мы не создаем экземпляр AccountViewModel. Поэтому вам нужно передать IGithubApi в качестве параметра конструктора в AccountViewModel, который будет жизнеспособным решением в этом сценарии.

class AccountViewModel @Inject constructor(private val api: IGithubAPI, app: Application) : AndroidViewModel(app)
1 голос
/ 02 июля 2019

Из вашей трассировки стека:

test.ui.login.models.AccountViewModel cannot be provided without an @Inject constructor or an @Provides-annotated method

У вашего AccountViewModel нет конструктора @Inject.

Попробуйте это:

class AccountViewModel @Inject constructor(
    private val gitHubApi: IGitHubApi, 
    app: Application) : AndroidViewModel(app) {
}
...