使用dagger2在多个片段中使用视图模型的相同实例

我在我的项目中仅使用dagger2(而不是dagger-android)。使用multibinding注入ViewModel很好。但是以前没有dagger2的地方存在一个问题,我在多个片段中使用了活动中使用的相同viewmodel实例(使用fragment-ktx方法activityViewModels()),但是现在由于dagger2注入了视图模型,因此它总是提供新实例(检查每个片段的viewmodel的每个片段中的hashCode),这只是中断了使用viewmodel的片段之间的通信。

片段和视图模型代码如下:

class MyFragment: Fragment() {
    @Inject lateinit var chartViewModel: ChartViewModel

    override fun onAttach(context: Context) {
        super.onAttach(context)
        (activity?.application as MyApp).appComponent.inject(this)
    }

}

//-----ChartViewModel class-----

class ChartViewModel @Inject constructor(private val repository: ChartRepository) : BaseViewModel() {
   //live data code...
}

这是视图模型依赖注入的代码:

//-----ViewModelKey class-----

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

//-----ViewModelFactory class------

@Singleton
@Suppress("UNCHECKED_CAST")
class ViewModelFactory
@Inject constructor(
    private val viewModelMap: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {

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

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

//-----ViewModelModule class-----

@Module
abstract class ViewModelModule {
    @Binds
    internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory

    @Binds
    @IntoMap
    @ViewModelKey(ChartViewModel::class)
    abstract fun bindChartViewModel(chartViewModel: ChartViewModel): ViewModel
}

有什么方法可以为多个片段实现相同的视图模型实例,也可以同时将视图模型注入片段中。 另外,也需要bindViewModelFactory方法,因为即使没有此方法,它似乎也对应用程序没有影响。

一种解决方法是为共享通用视图模型的片段制作BaseFragment,但这又包括样板代码,而且我也不是BaseFragment / BaseActivity的忠实拥护者。

这是为ChartViewModel生成的代码,该代码始终创建viewModel的newInstance:

@SuppressWarnings({
    "unchecked",
    "rawtypes"
})
public final class ChartViewModel_Factory implements Factory<ChartViewModel> {
  private final Provider<ChartRepository> repositoryProvider;

  public ChartViewModel_Factory(Provider<ChartRepository> repositoryProvider) {
    this.repositoryProvider = repositoryProvider;
  }

  @Override
  public ChartViewModel get() {
    return newInstance(repositoryProvider.get());
  }

  public static ChartViewModel_Factory create(Provider<ChartRepository> repositoryProvider) {
    return new ChartViewModel_Factory(repositoryProvider);
  }

  public static ChartViewModel newInstance(ChartRepository repository) {
    return new ChartViewModel(repository);
  }
}
评论
nodio
nodio

问题是当您像这样注入视图模型时

class MyFragment: Fragment() {
    @Inject lateinit var chartViewModel: ChartViewModel

匕首只是创建一个新的viewmodel实例。没有viewmodel-fragment-lifecycle魔术,因为此ViewModel不在活动/片段的ViewModel存储中,并且您创建的ViewModelFactory并未提供该ViewModel。在这里,您可以将viewmodel视为任何普通类。举个例子:

class MyFragment: Fragment() {
    @Inject lateinit var anything: AnyClass
}
class AnyClass @Inject constructor(private val repository: ChartRepository) {
   //live data code...
}

Your viewmodel is equivalent to this AnyClass because the viewmodel is not in the viewmodelstore and not scoped to the lifecycle of the fragment/activity.

有没有办法为多个片段实现相同的ViewModel实例,同时将片段中的视图模型注入

否。由于上述原因。

另外,也需要bindViewModelFactory方法,因为即使没有此方法,它似乎也对应用程序没有影响。

It does not have any effect because (I'm assuming that) you are not using the ViewModelFactory anywhere. Since it's not referenced anywhere, this dagger code for the viewmodelfactory is useless.

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

Here's what @binds is doing: 1 2

这就是为什么删除它对应用程序没有影响的原因。

那么解决方案是什么?您需要将工厂注入片段/活动中,并使用工厂获取视图模型的实例

class MyFragment: Fragment() {
    @Inject lateinit var viewModelFactory: ViewModelFactory

    private val vm: ChartViewModel by lazy {
        ViewModelProvider(X, YourViewModelFactory).get(ChartViewModel::class.java)
    }

What is X here? X is ViewModelStoreOwner. A ViewModelStoreOwner is something that keeps track of the viewmodels under them. ViewModelStoreOwner is implemented by activity and fragment. So you have a few ways of creating a viewmodel:

  1. 活动中的视图模型
ViewModelProvider(this, YourViewModelFactory)
  1. 片段中的视图模型
ViewModelProvider(this, YourViewModelFactory)
  1. 片段中的viewmodel范围为父片段,并在子片段之间共享
ViewModelProvider(requireParentFragment(), YourViewModelFactory)
  1. 片段中的viewmodel范围限定于父活动,并在活动下的各个片段之间共享
ViewModelProvider(requireActivity(), YourViewModelFactory)
一种解决方法是为共享通用视图模型的片段制作BaseFragment,但是它将再次包含样板代码,而且我也不是BaseFragment / BaseActivity的忠实粉丝

Yes, this is indeed a bad idea. The solution is to use requireParentFragment() and requireActivity() to get the viewmodel instance. But you'll be writing the same code multiple times in every fragment/activity that has a viewmodel. For that you can abstract away this ViewModelProvider(x, factory) part in a base fragment/activity class and also inject the factory in the base classes, which will simplify your child fragment/activity code like this:

class MyFragment: Fragment() {

    private val vm: ChartViewModel by bindViewModel() // or bindParentFragmentViewModel() or bindActivityViewModel()
点赞
评论
{醉相思}
{醉相思}

You can share ViewModel between fragments when instantiating if the fragments has the same parent activity

片段一

class FragmentOne: Fragment() {

private lateinit var viewmodel: SharedViewModel

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    viewmodel= activity?.run {
        ViewModelProviders.of(this).get(SharedViewModel::class.java)
    } : throw Exception("Invalid Activity")
  }
}

片段二

class FragmentTwo: Fragment() {

private lateinit var viewmodel: SharedViewModel

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    viewmodel= activity?.run {
        ViewModelProviders.of(this).get(SharedViewModel::class.java)
    } ?: throw Exception("Invalid Activity")

 }
}
点赞
评论
换位思考
换位思考

Add your ViewModel as PostListViewModel inside ViewModelModule:

@Singleton
class ViewModelFactory @Inject constructor(private val viewModels: MutableMap<Class<out ViewModel>, Provider<ViewModel>>) : ViewModelProvider.Factory {

    override fun <T : ViewModel> create(modelClass: Class<T>): T = viewModels[modelClass]?.get() as T
}

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

@Module
abstract class ViewModelModule {

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

    @Binds
    @IntoMap
    @ViewModelKey(PostListViewModel::class)
    internal abstract fun postListViewModel(viewModel: PostListViewModel): ViewModel

    //Add more ViewModels here
}

To end with, our activity will have ViewModelProvider.Factory injected and it will be passed to theViewModelProviders.of() method as the factory (second parameter)

class PostListActivity : AppCompatActivity() {

    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_post_list)
        getAppInjector().inject(this)
        val vm = ViewModelProviders.of(this, viewModelFactory)[PostListViewModel::class.java]
        vm.posts.observe(this, Observer(::updatePosts))
    }

    //...
}

For more check this post:Inject ViewModel with Dagger2 And Check github

点赞
评论