Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to call again LiveData Coroutine Block

I'm using LiveData's version "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha05". Once my LiveData block executes successfully I want to explicitly trigger it to execute again, e.g.

  1. I navigate to a fragment
  2. User's data loads
  3. I click delete btn while being in the same fragment
  4. User's data should refresh

I have a fragment where I observe my LiveData, a ViewModel with LiveData and Repository:

ViewModel:

  fun getUserLiveData() = liveData(Dispatchers.IO) {

   val userData = usersRepo.getUser(userId)

   emit(userData) 
}

Fragment:

viewModel.getUserLiveData.observe(viewLifecycleOwner,
            androidx.lifecycle.Observer {.. 

Then I'm trying to achieve desired behaviour like this:

viewModel.deleteUser()

viewModel.getUserLiveData()

According to the documentation below LiveData block won't execute if it has completed successfully and if I put a while(true) inside the LiveData block, then my data refreshes, however I don't want this to do since I need to update my view reactively.

If the [block] completes successfully or is cancelled due to reasons other than [LiveData] becoming inactive, it will not be re-executed even after [LiveData] goes through active inactive cycle.

Perhaps I'm missing something how I can reuse the same LiveDataScope to achieve this? Any help would be appreciated.

like image 744
JanasC12 Avatar asked Oct 05 '19 05:10

JanasC12


2 Answers

To do this with liveData { .. } block you need to define some source of commands and then subscribe to them in a block. Example:

MyViewModel() : ViewModel() {
   val commandsChannel = Channel<Command>()

   val liveData = livedata {
      commandsChannel.consumeEach { command -> 
            // you could have different kind of commands 
             //or emit just Unit to notify, that refresh is needed
           val newData = getSomeNewData()
           emit(newData)
       }
   }

  fun deleteUser() {
   .... // delete user
   commandsChannel.send(RefreshUsersListCommand)
  }
}

Question you should ask yourself: Maybe it would be easier to use ordinary MutableLiveData instead, and mutate its value by yourself?

livedata { ... } builder works well, when you can collect some stream of data (like a Flow / Flowable from Room DB) and not so well for plain, non stream sources, which you need to ask for data by yourself.

like image 182
Circusmagnus Avatar answered Nov 06 '22 17:11

Circusmagnus


I found a solution for this. We can use switchMap to call the LiveDataScope manually.

First, let see the official example for switchMap:

/**
 * Here is an example class that holds a typed-in name of a user
 * `String` (such as from an `EditText`) in a [MutableLiveData] and
 * returns a `LiveData` containing a List of `User` objects for users that have
 * that name. It populates that `LiveData` by requerying a repository-pattern object
 * each time the typed name changes.
 * <p>
 * This `ViewModel` would permit the observing UI to update "live" as the user ID text
 * changes.
**/
class UserViewModel: AndroidViewModel {
    val nameQueryLiveData : MutableLiveData<String> = ...

    fun usersWithNameLiveData(): LiveData<List<String>> = nameQueryLiveData.switchMap {
        name -> myDataSource.usersWithNameLiveData(name)
    }

    fun setNameQuery(val name: String) {
        this.nameQueryLiveData.value = name;
    }
}

The example was very clear. We just need to change nameQueryLiveData to your own type and then combine it with LiveDataScope. Such as:

class UserViewModel: AndroidViewModel {
    val _action : MutableLiveData<NetworkAction> = ...

    fun usersWithNameLiveData(): LiveData<List<String>> = _action.switchMap {
        action -> liveData(Dispatchers.IO){
            when (action) {
                Init -> {
                    // first network request or fragment reusing
                    // check cache or something you saved.
                    val cache = getCache()
                    if (cache == null) {
                        // real fecth data from network
                        cache = repo.loadData()
                    }
                    saveCache(cache)
                    emit(cache)
                }
                Reload -> {
                    val ret = repo.loadData()
                    saveCache(ret)
                    emit(ret)
                }
            }
        }
    }

    // call this in activity, fragment or any view
    fun fetchData(ac: NetworkAction) {
        this._action.value = ac;
    }

    sealed class NetworkAction{
        object Init:NetworkAction()
        object Reload:NetworkAction()
    }
}


like image 33
rosuh Avatar answered Nov 06 '22 16:11

rosuh