【问题标题】:How to make MVRX viewmodels work with dagger2?如何使 MVRX 视图模型与 dagger2 一起使用?
【发布时间】:2019-04-28 15:12:28
【问题描述】:

编辑

添加@ViewModelKey 并确保所有视图模型都有@Inject 注释就可以了

使用 Dagger2 Di 库和 ViewModelFactory 注入 ViewModel 导致缺少绑定构建错误。

我得到的错误如下:

 AppComponent.java:12: error: [Dagger/MissingBinding] java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> cannot be provided without an @Provides-annotated method. public abstract interface AppComponent extends dagger.android.AndroidInjector<com.honing.daggerexploration.DaggerExplorationApplication> {
                ^
      java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
          com.honing.daggerexploration.di.DaggerViewModelFactory(creators)
      com.honing.daggerexploration.di.DaggerViewModelFactory is injected at
          com.honing.daggerexploration.features.MainActivity.viewModelFactory
      com.honing.daggerexploration.features.MainActivity is injected at
          dagger.android.AndroidInjector.inject(T) [com.honing.daggerexploration.di.AppComponent → com.honing.daggerexploration.di.modules.ActivityModule_BindActivityMain.MainActivitySubcomponent]

我搜索了其他 stackoverflow 问题,但没有一个为我解决了这个问题。

我使用的Dagger版本是最新的,2.22.1

我敢打赌这个错误与 MVRX 无关,因为我能够在不涉及 mvrx 视图模型类的情况下在小型库中重现它,但是我的意图是最终将 dagger2 与 mvrx 框架一起使用并能够向它注入依赖项。

与此相关的一些代码:

DaggerExplorationApplication

class DaggerExplorationApplication : DaggerApplication() {
    override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
        return DaggerAppComponent.builder().create(this)
    }

}

DaggerViewModelFactory:

    /**
     * ViewModelFactory which uses Dagger to create the instances.
     */
    class DaggerViewModelFactory @Inject constructor(
        private val creators: @JvmSuppressWildcards Map<Class<out ViewModel>, Provider<ViewModel>>
    ) : ViewModelProvider.Factory {
        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 {
                @Suppress("UNCHECKED_CAST")
                return creator.get() as T
            } catch (e: Exception) {
                throw RuntimeException(e)
            }
        }
    }

ViewModelFactoryModule

@Module
abstract class ViewModelFactoryModule {

    @Binds
    abstract fun bindViewModelFactory(viewModelFactory: DaggerViewModelFactory): ViewModelProvider.Factory
}

活动模块

@Module
abstract class ActivityModule {


    @ContributesAndroidInjector(modules = [ViewModelFactoryModule::class])
    abstract fun bindActivityMain(): MainActivity
}

关于我用 dagger 实现 mvrx 的努力,据此我需要按方格使用 AssistedInject 库,我看了视频并且相当了解这背后的原因。然而,由于上述错误,我未能构建项目。 chrisbanes 关于这件事的一个有趣的帖子也在this link

带有 dagger2 的 MVRX ViewModels 已由 chrisbanes 使用 this project(Tivi) 成功实现,我尝试按照他们的做法进行操作,但我也失败了。帖子顶部描述的问题阻止了我。准备提供任何缺少的代码,如果需要能够解决此问题,请提供更多信息。

【问题讨论】:

  • 请添加AppComponent源代码

标签: android kotlin dagger-2 android-viewmodel dagger


【解决方案1】:

您缺少地图多绑定的配置。

Tivi 有@ViewModelKey:

/*
 * Copyright 2017 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package app.tivi.inject

import androidx.lifecycle.ViewModel
import dagger.MapKey
import kotlin.reflect.KClass

@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)

它有一个模块将 ViewModelKey 绑定到 ViewModel 的特定子类型,这样它就暴露为ViewModel(并用一个键标记):

/*
 * Copyright 2017 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
@Binds
@IntoMap
@ViewModelKey(PopularShowsViewModel::class)
abstract fun bindPopularShowsViewModel(viewModel: PopularShowsViewModel): ViewModel

@Binds
@IntoMap
@ViewModelKey(TrendingShowsViewModel::class)
abstract fun bindTrendingShowsViewModel(viewModel: TrendingShowsViewModel): ViewModel

@Binds
@IntoMap
@ViewModelKey(ShowDetailsNavigatorViewModel::class)
abstract fun bindDetailsNavigatorViewModel(viewModel: ShowDetailsNavigatorViewModel): ViewModel

所以你需要将这些多绑定配置设置为使用模块的组件。

同样重要的是,它们的 ViewModel 类具有 @Inject 带注释的构造函数才能使其正常工作。

【讨论】:

  • 如何请求注入到activity中?将 AndroidInjection.inject(this) 放在活动的 oncreate 中是正确的选择吗?还如何将活动定义为匕首设置上的可注入目标?我相信问题就在那里
  • 但我认为您已经拥有上面提到的@ContributesAndroidInjector。你可以把它放在那里,或者create an Application.ActivityLifecycleCallbacks and inject from there
  • true,chrisbanes 两者都做了,使用了 ContributesAndroidInjector 并使用了普通的绑定注解。当我检查代码时,我只看到他使用了绑定,所以我做到了。构建错误消失了,但 daggerviewmodelfactory 没有被注入。所以看起来 mainactivity 不再被定义为图中的可注入目标。在进一步检查 tivi 代码后,我发现他同时使用了这两种代码。顺便说一句,您包含的链接是一个非常好的想法!现在,即使我确实按照您提到的那样放置了缺失的部分,问题仍然存在:/
  • 新问题是什么?请更新问题,考虑到以前,您无法提供Map&lt;ViewModelKey, Provider&lt;ViewModel&gt;&gt;,因为未配置多绑定。如果你添加了这个,它仍然不起作用,那么应该有一些新的问题。
【解决方案2】:

这就是我使用 MVVM 架构使我的 Android 应用程序与 Dagger 2 一起工作的方式。它是 Java,但我希望它能够引导你朝着正确的方向前进。

应用组件

@ApplicationScope
@Component(modules = {
        AndroidInjectionModule.class,
        AppModule.class,
        ActivityBuilder.class
})
public interface AppComponent {

    void inject(MyApp app);

    @Component.Builder
    interface Builder {

        @BindsInstance
        Builder application(Application application);

        AppComponent build();
    }
}

ViewModelFactory

@ApplicationScope
public class ViewModelFactory implements ViewModelProvider.Factory {
    private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;

    @Inject
    public ViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
        this.creators = creators;
    }

    @SuppressWarnings("unchecked")
    @Override
    @NonNull
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        Provider<? extends ViewModel> creator = creators.get(modelClass);
        if (creator == null) {
            for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : creators.entrySet()) {
                if (modelClass.isAssignableFrom(entry.getKey())) {
                    creator = entry.getValue();
                    break;
                }
            }
        }
        if (creator == null) {
            throw new IllegalArgumentException("unknown model class " + modelClass);
        }
        try {
            return (T) creator.get();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

ViewModelModule

@Module
public abstract class ViewModelModule {
    @Binds
    @IntoMap
    @ViewModelKey(MainActivityViewModel.class)
    abstract ViewModel bindMainActivityViewModel(MainActivityViewModel mainActivityViewModel);

    // Same thing for each view model

    ...

}

ActivityBuilder

@Module
public abstract class ActivityBuilder {

    @ContributesAndroidInjector(modules = {
            MainActivityModule.class
            // Add the Provider of each child fragment's viewmodel.
    })
    public abstract MainActivity bindMainActivity();

    // Same for each new activity
}

ViewModelKey

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@MapKey
public @interface ViewModelKey {
    Class<? extends ViewModel> value();
}

应用范围

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ApplicationScope {
}

AppModule

@Module(includes = {
        ViewModelModule.class,
})
public class AppModule {

    // Provides all the things needed for the whole application, such as Daos, Retrofit interface, contexts, etc.

}

我的应用程序

public class MyApp extends Application implements HasActivityInjector {

    @Inject
    DispatchingAndroidInjector<Activity> activityDispatchingAndroidInjector;


    @Override
    public void onCreate() {

        DaggerAppComponent
                .builder()
                .application(this)
                .build()
                .inject(this);

        super.onCreate();
    }

    @Override
    public DispatchingAndroidInjector<Activity> activityInjector() {
        return activityDispatchingAndroidInjector;
    }
}

MainActivity

public class MainActivity extends AppCompatActivity {
    @Inject
    ViewModelProvider.Factory mViewModelFactory;

    private MainActivityViewModel mViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        AndroidInjection.inject(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(MainActivityViewModel.class);

        ...
     }
}

MainActivityModule

@Module
public class MainActivityModule {

    //Provides all the things needed just for your MainActivity

}

MainActivityViewModel

public class MainActivityViewModel extends ViewModel {

    // Member variables

    @Inject
    public MainActivityViewModel(... things to inject into the viewmodel, such as daos, repositories, contexts, etc. ... ) {
         ...
    }

}

所以,它看起来需要很多配置,但是一旦您完成了初始设置,在此基础上进行构建会更容易。

总结一下:

  • 创建注解@ApplicationScope(或使用默认的@Singleton),更重要的是,创建@ViewModelKey注解。
  • 让您的应用程序类实现HasActivityInjector 并使用DaggerAppComponent 构建器进行注入。
  • 完全按照我之前提到的方式实现 AppComponentViewModelFactory
  • 定义您的AppModule 以提供您的应用所需的所有内容。但请记住包含ViewModelModule,因为它负责提供 ViewModel。
  • 每次您想添加一个带有自己的 ViewModel 的新活动时,您必须执行以下操作:
    1. 创建ActivityActivityModuleActivityViewModel
    2. ViewModelModule 中添加一个条目以绑定视图模型。
    3. ActivityBuilder 中添加一个条目以提供活动。
    4. 享受吧。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-07-19
    • 1970-01-01
    • 2022-08-19
    • 2020-01-20
    • 2018-10-25
    • 2020-06-27
    • 1970-01-01
    • 2011-03-12
    相关资源
    最近更新 更多