简体   繁体   中英

How to know when job from viewModel is done

I am trying to figure out how jobs with coroutines work. Basically, I want to launch this coroutine from FirstFragment and after that navigate to SecondFragment and get notified when this job is done. I call getData() in FirstFragment onViewCreated() and navigate to SecondFragment . Whether I write getData().isCompleted or getData().invokeOnCompletion { } in SecondFragment nothing happens. I don't know if I am missing something or not starting job correctly or something else.

private val _data = MutableStateFlow<GetResource<String>?>(null)
val data: StateFlow<GetResource<String>?> = _data

fun getData() = viewModelScope.launch {
    repository.getData().collect {
        _data.value = it
    }
}

A Flow from a database never completes because it is supposed to monitor the database for changes indefinitely. It only stops when the coroutine is cancelled. Therefore the Job that collects such a Flow will never complete. Also, if you call getData() on the repo again, you are getting a new Flow instance each time.

Regardless of what you're doing, you need to be sure you are using the same ViewModel instance between both fragments by scoping it to the Activity. (Use by activityViewModels() for example.) This is so the viewModelScope won't be cancelled during the transition between Fragments.

If all you need is a single item from the repo one time, probably the simplest thing to do would be to expose a suspend function from the repo instead of a Flow. Then turn it into a Deferred. Maybe by making it a Lazy , you can selectively decide when to start retrieving the value. Omit the lazy if you just want to start retrieving the value immediately when the first Fragment starts.

// In the shared view model:
val data: Deferred<GetResource<String>> by lazy { 
    viewModelScope.async {
      repository.getData() // suspend function returning GetResource<String>
    }
  }

fun startDataRetrieval() { data } // access the lazy property to start its coroutine

// In second fragment:
lifecycleScope.launch {
  val value = mySharedViewModel.data.await()
  // do something with value
}

But if you have to have the Flow because you're using it for other purposes:

If you just want the first available value from the Flow, have the second Fragment monitor your data StateFlow for its first valid value.

lifecycleScope.launch {
  val value = mySharedViewModel.data.filterNotNull().first()
  // do something with first arrived value
}

And you can use SharedFlow so you don't have to make the data type nullable. If you do this you can omit filterNotNull() above. In your ViewModel, it's easier to do this with shareIn than your code that has to use a backing property and manually collect the source.

val data: SharedFlow<GetResource<String>> = repository.getData()
  .shareIn(viewModelScope, replay = 1, SharingStarted.Eagerly)

If you need to wait before starting the collection to the SharedFlow, then you could make the property lazy.

Agreed with @Tenfour04 's answer, I would like to contribute a little more. If you really want to control over the jobs or Structured Concurrency , i would suggest use custom way of handling the coroutine rather than coupled your code with the viewModelScope .

There are couple of things you need to make sure:
1- What happen when cancellation or exception occurrs
2- you have to manage the lifecycle of the coroutine(CoroutineScope)
3- Cancelling scope, depends on usecase like problem facing you are right now
4- Scope of ViewModel eg: Either it is tied to activity(Shared ViewModel) or for specific fragment.

If you are not handling either of these carefully specifically first 3, your are more likely to leaking the coroutine your are gurenteed gonna get misbehavior of you app.
Whenever you start any coroutine in Custom way you have to make sure, what is going to be the lifecycle, when it gonna end, This is so important, it can cause real problems
Luckily, i have this sample of CustomViewModel using Jobs: Structured Concurrency sample code

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM