Search code examples
androidmvvmandroid-viewmodel

ViewModel Instantiation Causes Infinite Loop


I'm using a pattern that I've used a few times before to instantiate a ViewModel object. In this case, the data is saved as a string in SharedPreferences. I just need to read that string, parse it to the correct object, and assign that object as the value to my view model.

But when I do the assignment, I create an infinite loop.

class UserDataViewModel(private val prefs: SharedPreferences): ViewModel() {

    val userData: MutableLiveData<UserData> by lazy {
        MutableLiveData<UserData>().also {
            val userDataString = prefs.getString(Authenticator.USER_DATA, "")
            val ud = Gson().fromJson(userDataString, UserData::class.java)
            userData.value = ud // infinite loop is here
        }
    }

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

This is in onCreateView() of the fragment that keeps the reference to the ViewModel:

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

userDataViewModel
    .getUserData()
    .observe(this, Observer {
        binding.userData = userDataViewModel.userData.value
    })

FWIW, in the fragment, I have break points on both getUserData() and on binding.userData.... The last break point that gets hit is on getUserData().

I don't see where the loop is created. Thanks for any help.


Solution

  • The userData field is only initialized once the by lazy {} block returns. You're accessing the userData field from within the by lazy {} block and that's what is creating the loop - the inner access sees that it hasn't finishing initializing, so it runs the block again..and again and again.

    Instead, you can access the MutableLiveData you're modifying in the also block by using it instead of userData, breaking the cycle:

    val userData: MutableLiveData<UserData> by lazy {
        MutableLiveData<UserData>().also {
            val userDataString = prefs.getString(Authenticator.USER_DATA, "")
            val ud = Gson().fromJson(userDataString, UserData::class.java)
            it.value = ud
        }
    }