Android 同一活动中跨片段的ViewModel作用域

Android 同一活动中跨片段的ViewModel作用域,android,android-fragments,android-livedata,android-architecture-components,android-viewmodel,Android,Android Fragments,Android Livedata,Android Architecture Components,Android Viewmodel,我有一个问题,我想可以追溯到ViewModelscoping。在同一活动下,同一导航图中有两个片段。第一个片段,LoginFragment实例化了一个UserDataViewModel,并将其部分数据填充到名为UserData 第二个片段,TodayFragment应该选择UserDataViewModel,并使用已经填充的属性来做进一步的工作。但是,当TodayFragment尝试访问UserData对象时,该对象为空。要强调的是,UserDataViewModel仍然具有相同的内存地址,但问

我有一个问题,我想可以追溯到
ViewModel
scoping。在同一活动下,同一导航图中有两个片段。第一个片段,
LoginFragment
实例化了一个
UserDataViewModel
,并将其部分数据填充到名为
UserData

第二个片段,
TodayFragment
应该选择
UserDataViewModel
,并使用已经填充的属性来做进一步的工作。但是,当
TodayFragment
尝试访问
UserData
对象时,该对象为空。要强调的是,
UserDataViewModel
仍然具有相同的内存地址,但问题是它丢失了对其数据值的引用

我已经对其进行了精简,因此数据只需从
SharedReferences
中提取一个字符串,并使用GSON对其进行解析

登录片段

private lateinit var userDataViewModel: UserDataViewModel

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    MyApplication.appComponent?.inject(this)
    prefs = PreferenceManager.getDefaultSharedPreferences(context)

    userDataViewModel = activity?.run {
        ViewModelProviders
            .of(this, UserDataViewModelFactory(prefs, dataFetcherService))
            .get(UserDataViewModel::class.java)
    } ?: throw Exception("Invalid Activity")

    userDataViewModel.getUserData()
}
今天片段

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    prefs = PreferenceManager.getDefaultSharedPreferences(activity)

    userDataViewModel = activity?.run {
        ViewModelProviders
            .of(this, UserDataViewModelFactory(prefs, dataFetcherService))
            .get(UserDataViewModel::class.java)
    } ?: throw Exception("Invalid Activity")
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    userDataViewModel
        .getUserData()
        .observe(this, Observer {
            clubId = it.club_id  // <-- this is where it crashes since 'it' is null
        })
}
class UserDataViewModel(
    prefs: SharedPreferences,
    dataFetcherService: DataFetcherService)
    : ViewModel() {

    private val useCase = UserDataUseCase(prefs, dataFetcherService)

    val userData: MutableLiveData<UserData> by lazy {
        MutableLiveData<UserData>().apply { loadUserData() }
    }

    fun getUserData(): LiveData<UserData> {
        return userData
    }

    private fun loadUserData() {
        viewModelScope.launch {
            withContext(Dispatchers.IO) {
                try {
                    return@withContext useCase.loadUserData(USER_UNKNOWN)
                } catch (t: Throwable) {
                    return@withContext null
                }
            }
                ?.let {
                    userData.postValue(it)
                }
        }
    }
}
override-fun-onCreate(savedInstanceState:Bundle?){
super.onCreate(savedInstanceState)
prefs=PreferenceManager.GetDefaultSharedReferences(活动)
userDataViewModel=活动?。运行{
ViewModelProviders
.of(这是UserDataViewModelFactory(prefs,dataFetcherService))
.get(UserDataViewModel::class.java)
}?:抛出异常(“无效活动”)
}
覆盖已创建的视图(视图:视图,保存状态:捆绑?){
super.onViewCreated(视图,savedInstanceState)
userDataViewModel
.getUserData()
观察,观察{
clubId=it.club_id/根据

ViewModel将一直保留在内存中,直到其作用域的生命周期永久消失:对于活动,当它完成时,而对于片段,当它分离时。

这意味着,视图生命周期模型绑定到活动/片段,而不是应用程序

ViewModelProviders在某些情况下重用viewmodel,但当您在屏幕上从LoginFragment导航到TodayFragment时,由提供者创建并绑定到LoginFragment的viewmodel将被销毁,然后创建新的viewmodel并绑定到TodayFragment

我想这就是你得到空引用的原因

注:
我无法解释为什么新旧内存地址相同。根据我的经验,如果没有其他操作覆盖释放的内存,则可以重用它。这可能就是内存地址没有更改的原因。

在创建ViewModel时,传递活动上下文而不是片段上下文。ViewModels将与活动li一样有效ve

val activityContext = getActivity()
ViewModelProviders.of(activityContext, ...)

在每个片段中,您将通过以下方式获得
ViewModel
的实例:

ViewModelProviders
        .of(this, UserDataViewModelFactory(prefs, dataFetcherService))
请注意
这个
,它指向
片段。这个
。这意味着,作为
生命周期所有者
您正在提供
片段
的实例。因此,每个片段都将创建自己的
UserDataViewModel
的新实例

相反,您真正想要的是重用
ViewModel
的同一实例,这意味着作为
LifecycleOwner
您应该传入托管活动:

ViewModelProviders
        .of(requireActivity(), UserDataViewModelFactory(prefs, dataFetcherService))

这样,您将在两个
片段中获得相同的
ViewModel
实例。在这种情况下,您可以使用navGraphViewModels()属性委托从片段目标中检索ViewModel,这样您就可以访问“附加”到导航图的ViewModel

因此,通常您可以在
LoginFragment
TodayFragment

private val userDataViewModel: UserDataViewModel by navGraphViewModels(R.id.youNavGraphID)
但在您的情况下,在初始化视图模型时,您正在将参数传递到视图模型中,因此您还必须传递一个
ViewModelProvider.Factory

private val sharedViewModel: BookViewModel by navGraphViewModels(R.id.navGraph, ::viewModelProducer)

fun viewModelProducer(): ViewModelProvider.Factory {
    return object : ViewModelProvider.Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            return UserDataViewModelFactory(prefs, dataFetcherService) as T
        }
    }
}
---更新---

我只是面临同样的情况。我会保留我以前的答案,但我相信下面的解决方案应该能解决你的问题

初始化活动/片段作用域视图模型时,不应在
ViewModelProviders.of()
中传递
this
,因为这将意味着
片段本身而不是活动

 userDataViewModel = activity?.run {
    ViewModelProviders
        .of(this, UserDataViewModelFactory(prefs, dataFetcherService))
        .get(UserDataViewModel::class.java)
} ?: throw Exception("Invalid Activity")
相反,您应该使用以下活动

userDataViewModel = activity?.run {
    ViewModelProviders
        .of(it, UserDataViewModelFactory(prefs, dataFetcherService))
        .get(UserDataViewModel::class.java)
} ?: throw Exception("Invalid Activity")

希望我能理解你的问题,这里有一些建议

  • 尝试将ViewModel更改为
    类UserDataViewModel(应用程序:应用程序):AndroidViewModel(应用程序)
    。以便实例与应用程序生命周期相关联

  • 设置clubId可以是
    it?。应用{setClubId(club_id)}
    以便应用程序在实际数据可用时运行

  • 每个片段都应该彼此独立,这样您就可以使用诸如
    view.findNavController().navigate(R.id.action\u today\u Fragment)
    从LoginFragment内部进行导航。基本上,您完全依赖图形进行导航。并使用ViewModel保存数据

  • 此外,不应从活动/片段中传入数据源(例如,您的SharedPref使用似乎不正确)

    Activities/Fragments <--- ViewModel <------ DataSources
    

    Activities/Fragments您如何定义implement
    UserDataViewModelFactory
    ?当activity被销毁时,您会在ViewModel中留下一个对它的引用。@RonTLV那么如何解释这一点,google建议:为什么要对答案进行向下投票,传递activity context会为.UPVOTE.Thi中的所有片段保留公共视图模型s是推荐的方式。
    Activities/Fragments <--- ViewModel <------ DataSources