Vuk Bibic
Vuk Bibic

Reputation: 1455

Observing LiveData from ViewModel

I have a separate class in which I handle data fetching (specifically Firebase) and I usually return LiveData objects from it and update them asynchronously. Now I want to have the returned data stored in a ViewModel, but the problem is that in order to get said value, I need to observe the LiveData object returned from my data fetching class. The observe method required a LifecycleOwner object as the first parameter, but I obviously don't have that inside of my ViewModel and I know I am not supposed to keep a reference to the Activity/Fragment inside of the ViewModel. What should I do?

Upvotes: 135

Views: 143319

Answers (10)

Shriharsh Pathak
Shriharsh Pathak

Reputation: 166

Most of the answers here are kind of wrong.

There is a reason why we are supposed to pass lifecycleOwner to the observer. We should never observe anything from ViewModels

Never ever pass your activity, context or lifecycleOwner to the viewModel. It all togather breaks the MVVM principle.

Here is how you get things done.

Suppose, there is a liveData coming from FireBase -> LiveData<FirsbaseResult>

You will just create a local liveData variable inside your viewModel like following:

lateinit var localLiveData: LiveData<FirebaseResult>

Then, just assign the coming liveData to your localLiveData.

class MyViewModel : ViewModel() {
    
        lateinit var localLiveData: LiveData<FirebaseResult>
        
        fun getFireBaseData() {
            val firebaseLiveData = getResultFromFireBase() // Call function to get firebaseResult live Data
            localLiveData = firebaseLiveData
        }
    }

and now, you can easily observe this liveData inside your activity/fragment using Activity's scope.

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

        val myViewModel= ViewModelProvider(requireActivity()).get(MyViewModel::class.java)
        myViewModel.getFireBaseData()
        myViewModel.localLiveData.observe(viewLifecycleOwner) {
            // Happy Coding Here...
        }
    }

Upvotes: 1

J. Doe
J. Doe

Reputation: 13103

I do it as followed without external libraries:

  • In your ViewModel add a nullable property of type Job
  • Add your code that you want to execute also in a property, like this:

Code:

private val watcherNotifications = Observer<MyType> {
    // Logic...
}
  • Invoke the method observeForever on your LiveData and assign the resulting Job to your just-added property. The value is watcherNotifications or whatever you may have called it.
  • Override method onClear on your ViewModel and call on your job property method removeObserver(watcherNotifications)

Upvotes: 0

MJ Studio
MJ Studio

Reputation: 4631

Use Flow

The guideline in docs is misunderstood

However ViewModel objects must never observe changes to lifecycle-aware observables, such as LiveData objects.

In this Github issue, he describes that the situations that be applied the above rule are that observed lifecycle-aware observables are hosted by another lifecycle scope. There is no problem that observes LiveData in ViewModel contains observed LiveData.

Use Flow

class MyViewModel : ViewModel() {
    private val myLiveData = MutableLiveData(1)

    init {
        viewModelScope.launch {
            myLiveData.asFlow().collect {
                // Do Something
            }
        }
    }
}

Use StateFlow

class MyViewModel : ViewModel() {
    private val myFlow = MutableStateFlow(1)
    private val myLiveData = myFlow.asLiveData(viewModelScope.coroutineContext)
}

Requires "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" dependency

PS

The asFlow makes a flow that makes LiveData activate at starting collect. I think the solution with MediatorLiveData or Transformations and attaching a dummy observer doesn't have differences using the Flow except for emitting value from LiveData is always observed in the ViewModel instance.

Upvotes: 50

Jeffrey
Jeffrey

Reputation: 2106

As an example, if you need to fetch an ID (as LiveData) and use it to make another call for LiveData. Store the ID in selectedID and the Transformations would observe that field and call getAllByID(selectedID) (also LiveData) whenever it changes.

var selectedID = MutableLiveData<Int>()

val objects = Transformations.switchMap(selectedID) { getAllByID(it) }

Upvotes: 2

Parkinson
Parkinson

Reputation: 71

Its been a while since the original post but I recently stumbled upon the same issue (also with Firebase) and I was able to solve it with Transformations.

I have a repository class that holds liveData objects, collected with Firebase's ValueEventListener. The ViewModel hold a reference to this repository.

Now, in the ViewModel, instead of having a function that returns the LiveData value from the repository, and then pass it to the Fragment via an observer , like this:

fun getMyPayments(): LiveData<HashMap<String, Int>> {
    return repository.provideMyPayments()
}

I use a val with Transformations.map that represent the final result of the LiveData, after being processed by another function in the ViewModel:

val myRoomPaymentsList : LiveData<HashMap<String, HashMap<String, Payment>>> = Transformations.map(repository.provideMyPayments()) {data ->
        getRoomPaymentsList(data)
    }

note that the first parameter is the data source which you observe and the second parameter is the result you want to get. This val is a LiveData val that holds to the most current value from the repository and serves it as needed in the Fragment, keeping all the processing in the ViewModel and only the UI function inside the Framgent itself.

Then, inside my Fragment, I put an observer on this val:

viewModel.myRoomPaymentsList.observe(viewLifecycleOwner, {
    roomPayments = it
    graphFilterPeriod()
})

Upvotes: 2

apksherlock
apksherlock

Reputation: 8371

I know there have already been amazing answers for this topic, but I wanted to add my own as well:

If you want to stick to LiveData you can always use Transformations.map so that you don't have to observe in the ViewModel but rather only in the Fragment/Activity.

Otherwise, you can use SharedFlow, a single event observable. For more, I have written an article here: https://coroutinedispatcher.com/posts/decorate_stateful_mvvm/

You don't have to pass viewLifecycleOwner in the ViewModel because there is no point in calling observe in the ViewModel when the View just needs the latest result after all.

Upvotes: 0

guglhupf
guglhupf

Reputation: 1348

In this blog post by Google developer Jose Alcérreca it is recommended to use a transformation in this case (see the "LiveData in repositories" paragraph) because ViewModel shouldn't hold any reference related to View (Activity, Context etc) because it made it hard to test.

Upvotes: 45

Psijic
Psijic

Reputation: 1012

Use Kotlin coroutines with Architecture components.

You can use the liveData builder function to call a suspend function, serving the result as a LiveData object.

val user: LiveData<User> = liveData {
    val data = database.loadUser() // loadUser is a suspend function.
    emit(data)
}

You can also emit multiple values from the block. Each emit() call suspends the execution of the block until the LiveData value is set on the main thread.

val user: LiveData<Result> = liveData {
    emit(Result.loading())
    try {
        emit(Result.success(fetchUser()))
    } catch(ioException: Exception) {
        emit(Result.error(ioException))
    }
}

In your gradle config, use androidx.lifecycle:lifecycle-livedata-ktx:2.2.0 or higher.

There is also an article about it.

Update: Also it's possible to change LiveData<YourData> in the Dao interface. You need to add the suspend keyword to the function:

@Query("SELECT * FROM the_table")
suspend fun getAll(): List<YourData>

and in the ViewModel you need to get it asynchronously like that:

viewModelScope.launch(Dispatchers.IO) {
    allData = dao.getAll()
    // It's also possible to sync other data here
}

Upvotes: 10

siddharth
siddharth

Reputation: 399

I think you can use observeForever which does not require the lifecycle owner interface and you can observe results from the viewmodel

Upvotes: 29

Desmond Lua
Desmond Lua

Reputation: 6290

In ViewModel documentation

However ViewModel objects must never observe changes to lifecycle-aware observables, such as LiveData objects.

Another way is for the data to implement RxJava rather than LiveData, then it won't have the benefit of being lifecycle-aware.

In google sample of todo-mvvm-live-kotlin, it uses a callback without LiveData in ViewModel.

I am guessing if you want to comply with the whole idea of being lifecycle-ware, we need to move observation code in Activity/Fragment. Else, we can use callback or RxJava in ViewModel.

Another compromise is implement MediatorLiveData (or Transformations) and observe (put your logic here) in ViewModel. Notice MediatorLiveData observer won't trigger (same as Transformations) unless it's observed in Activity/Fragment. What we do is we put a blank observe in Activity/Fragment, where the real work is actually done in ViewModel.

// ViewModel
fun start(id : Long) : LiveData<User>? {
    val liveData = MediatorLiveData<User>()
    liveData.addSource(dataSource.getById(id), Observer {
        if (it != null) {
            // put your logic here
        }
    })
}

// Activity/Fragment
viewModel.start(id)?.observe(this, Observer {
    // blank observe here
})

PS: I read ViewModels and LiveData: Patterns + AntiPatterns which suggested that Transformations. I don't think it work unless the LiveData is observed (which probably require it to be done at Activity/Fragment).

Upvotes: 36

Related Questions