Search code examples
androidkotlinkotlin-coroutinesmutablelivedata

How to extract the information from MutableLiveData<Resource<Object>> to a simpler object in viewmodel


I am trying to get an object from a MutableLiveData<Resource<ObjectIWant>> to a var hereIwantTheObject : ObjectIWant in my HomeFragment, apparently use the Resource class is the recommended way if you use coroutines and LiveData. The thing is that I want this object on my variable and disatached from MutableLiveData, when I try it says the variable hasn't been initialized. Here my classes and what I have tried

ViewModel class:

class HomeFragmentViewModel : ViewModel() {

    val mutableVar: MutableLiveData<Resource<ObjectIwant>> = MutableLiveData()

    init {
        getObjectIwantData()
    }

    fun getObjectIwantData() = viewModelScope.launch {
        mutableVar.postValue(Resource.Loading())
        val response = AppRepository().getObjectIwantDataInRepository()
        mutableVar.postValue(handleResponse(response))
    }

    private fun handleResponse(response: Response<ObjectIwant>): Resource<ObjectIwant> {
        if (response.isSuccessful) {
            response.body()?.let { resultResponse ->
                return Resource.Success(resultResponse)
            }
        }
        return Resource.Error(response.message())
    }

HomeFragment class and where I am trying to get the info:

class HomeFragment : Fragment() {

    lateinit var viewModel: HomeFragmentViewModel

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = DataBindingUtil.inflate(inflater, R.layout.fragment_home, container, false)
        viewModel = ViewModelProvider(this).get(HomeFragmentViewModel::class.java)
        binding.lifecycleOwner = this

        var hereIwantTheObject: ObjectIwant? = null

        viewModel.brastlewarkTownData.observe(viewLifecycleOwner, Observer { response ->
            response.data?.let { brastlewarkTown ->
                hereIwantTheObject = brastlewarkTown
            }
        })
        Log.i(TAG, "onCreateView: ${hereIwantTheObject!!.name")
        return binding.root
    }
}

Here the Resource class:

sealed class Resource<T>(
    val data: T? = null,
    val message: String? = null
) {
    class Success<T>(data: T) : Resource<T>(data)
    class Error<T>(message: String, data: T? = null) : Resource<T>(data, message)
    class Loading<T> : Resource<T>()
}

Solution

  • When onCreateView() is executed, several things happen:

    • a local var hereIwantTheObject is declared and initialised with null
    • an observer for the LiveData<Resource<ObjectIwant>> object is registered
    • the value of the local variable is still null but you promise it is not null when writing to Logcat
    • so at this point your app will crash...

    But why is hereIwantTheObject still null? That's because the observation results for any LiveData objects can only come in on the UI thread after execution of the current function onCreateView() has finished.

    At this point however the local variable will have been discarded, so if you want to be able to assign a value to it later on you need to make it a property:

    private var hereIwantTheObject: String? = null
    

    Now if you delete the line with the local declaration for hereIwantTheObject and move the Log.i(...) statement to the block which is executed if there are changes to the LiveData object, everything will work as desired

    response.data?.let { brastlewarkTown ->
            hereIwantTheObject = brastlewarkTown
            Log.i(TAG, "onCreateView: ${hereIwantTheObject!!.name}")
        }