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

问题描述 投票:0回答:1

我只用dagger2 (不是匕首和甲骨文)在我的项目中。使用multibinding注入ViewModel时工作得很好,但有一个问题,以前没有dagger2时,我在多个片段中使用活动中使用的同一个viewModel实例(使用fragment-ktx方法activityViewModels())。但是有一个问题,以前没有dagger2的时候,我是在多个片段中使用activity中使用的同一个viewmodel实例(使用fragment-ktx方法activityViewModels()),但是现在由于 dagger2在注入视图模型时,总是给新的实例。 (在每个fragment中用hashCode检查)的viewmodel的每个fragment,这只是中断了fragment之间使用viewmodel的通信。

fragment &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...
}

这是viewmodel依赖注入的代码。

//-----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
}

有没有什么办法 实现多个片段的同一个viewmodel实例,同时在片段中注入view模型。另外,是否需要bindViewModelFactory方法,因为即使没有这个方法,似乎对应用也没有影响。

一个 变通办法 可能是为了使 基礎片段 (BaseFragment) 为共享视图模型的片段,但这将再次包含模板代码,而且我也不是BaseFragmentBaseActivity的忠实粉丝。

这是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);
  }
}
android android-fragments viewmodel dagger-2 android-mvvm
1个回答
1
投票

问题是,当你像这样注入viewmodel的时候

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

dagger只是创建了一个新的viewmodel实例。没有任何viewmodel-fragment-lifecycle的魔法发生,因为这个viewmodel不在activityfragment的viewmodelstore中,也没有被你创建的viewmodelfactory提供。在这里,你可以把viewmodel看成任何一个正常的类,真的。举个例子。

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

你的viewmodel就相当于这个 AnyClass 因为viewmodel不在viewmodelstore中,也不在fragmentactivity的生命周期内。

有什么办法可以实现多个fragment的viewmodel的同一个实例,同时也可以在fragment中注入viewmodel。

没有,因为上面列出的原因。

另外,还有没有必要使用bindViewModelFactory方法,因为即使没有这个方法,它似乎也不会对应用产生任何影响。

它没有任何影响是因为(我假设)你没有使用 ViewModelFactory 的任何地方。由于它没有被引用到任何地方,这个viewmodelfactory的匕首代码是没有用的。

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

下面是@binds的做法。1 2

所以删除它对应用没有影响。

那么有什么解决办法呢?你需要将工厂注入到fragmentactivity中,并使用工厂获得viewmodel的实例。

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

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

什么是 X 在这里?X是 ViewModelStoreOwner. A ViewModelStoreOwner 是有viewmodels下的东西。ViewModelStoreOwner 是由activity和fragment实现的。所以你有几种创建viewmodel的方法。

  1. viewmodel in activity
ViewModelProvider(this, YourViewModelFactory)
  1. 片段视图模型
ViewModelProvider(this, YourViewModelFactory)
  1. 片段(B)中的viewmodel作用于父片段(A),并在A下的子片段中共享。
ViewModelProvider(requireParentFragment(), YourViewModelFactory)
  1. 片段中的viewmodel作用于父活动,并在活动下的各个片段中共享。
ViewModelProvider(requireActivity(), YourViewModelFactory)

一个变通的办法是为片段制作一个共享视图模型的BaseFragment,但这将再次包含模板代码,而且我也不是很喜欢BaseFragmentBaseActivity。

是的,这的确是个坏主意。解决办法是使用 requireParentFragment()requireActivity() 来获取viewmodel的实例,但你会在每个有viewmodel的fragmentactivity中写同样的东西。但你会在每个有viewmodel的fragmentactivity中写同样的东西。为了避免这种情况,你可以抽象出这个 ViewModelProvider(x, factory) 部分在基类fragmentactivity类中,同时在基类中注入工厂,这样可以简化你的子类fragmentactivity代码。

class MyFragment: Fragment() {

    private val vm: ChartViewModel by bindViewModel() // or bindParentFragmentViewModel() or bindActivityViewModel()

0
投票

你可以共享 ViewModel 当实例化时,如果片段具有相同的父活动,则在片段之间的活动。

FragmentOne

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")
  }
}

FragmentTwo

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")

 }
}

0
投票

将您的ViewModel添加为 PostListViewModel 里面 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
}

最后,我们的活动将有: ViewModelProvider.Factory 注入,它将被传递到private val viewModel: PostListViewModel by viewModels { viewModelFactory }

class PostListActivity : AppCompatActivity() {

    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory
    private val viewModel: PostListViewModel by viewModels { viewModelFactory }


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_post_list)
        getAppInjector().inject(this)
        viewModel.posts.observe(this, Observer(::updatePosts))
    }

    //...
}

更多内容请看这篇文章。用Dagger2注入ViewModel。 还有 查看github

© www.soinside.com 2019 - 2024. All rights reserved.