I'm having a problem that I think traces back to ViewModel scoping. I have two fragments in the same nav graph under the same activity. The first fragment, LoginFragment instantiates a UserDataViewModel and populates some of its data in an object called UserData
The second fragment, TodayFragment is supposed to pick up UserDataViewModel and use the attributes already populated to do further work. However, by the time that TodayFragment attempts to access the UserData object, that object is null. To emphasize, UserDataViewModel still has the same memory address, but the problem is that it has lost the reference to its data value.
I've stripped this down so that the data is simply pulling a string from SharedPreferences and parsing it using GSON.
LoginFragment:
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()
}
TodayFragment
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
})
}
UserDataViewModel.kt
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)
}
}
}
}
Where it is weird is in the getUserData() method in the view model. When I set a breakpoint on these lines:
return userData
MutableLiveData<UserData>().apply { loadUserData() }
return@withContext useCase.loadUserData(USER_UNKNOWN)
those breakpoints are all hit the LoginFragment. But when it enters TodayFragment, it hits on return userData in the view model and then immediately jumps back to the observer inside of TodayFragment with a null pointer exception for the value inside of the view model.
I think that makes sense to the extent that the view model itself is instantiated, but why is the value inside the view model null?
For debugging purposes, I am hardcoding a response from the usecase and repository to ensure that there is a value populated in the viewmodel.
Screen Shot from LoginFragment 
Screen Shot from TodayFragment (extends AbstractWorkoutFragment)
Note how mData is now null but the ViewModel is the same. 
from ViewModel Scoping Across Fragments in Same Activity
No comments:
Post a Comment