How to call a function from in ViewModel in viewModelScope? - android

In the repository class have this listener:
override fun getState(viewModelScope: CoroutineScope) = callbackFlow {
val listener = FirebaseAuth.AuthStateListener { auth ->
trySend(auth.currentUser == null)
}
auth.addAuthStateListener(listener)
awaitClose {
auth.removeAuthStateListener(listener)
}
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), auth.currentUser == null)
In my ViewModel class I call getState function that returns a StateFlow<Boolean> using:
fun getState() = repo.getState(viewModelScope)
And I collect the data:
setContent {
val state = viewModel.getState().collectAsState().value
}
If I change in ViewModel:
fun getState() = viewModelScope.launch {
repo.getState(this)
}
So it can be called from a viewModelScope, I cannot collect the data anymore, as .collectAsState() appears in red. How to solve this? Any help would be greatly appreciated.

I'm not sure why you're trying to do this:
fun getState() = viewModelScope.launch {
repo.getState(this)
}
This code launches an unnecessary coroutine (doesn't call any suspending or blocking code) that get's a StateFlow reference and promptly releases the reference, and the function itself returns a Job (the launched coroutine). When you launch a coroutine, the coroutine doesn't produce any returned value. It just returns a Job instance that you can use to wait for it to finish or to cancel it early.
Your repository function already creates a StateFlow that runs in the passed scope, and you're already passing it viewModelScope, so your StateFlow was already running in the viewModelScope in your original code fun getState() = repo.getState(viewModelScope).

Use live data to send your result of state flow from view model to activity.
In your view model do like this:
var isActive = MutableLiveData<Boolean>();
fun getState() {
viewModelScope.launch {
repo.getState(this).onStart {
}
.collect(){
isActive.value = it;
}
}
}
In your activity observer your liveData like this:
viewModel.isActive.observe(this, Observer {
Toast.makeText(applicationContext,it.toString(),Toast.LENGTH_LONG).show()
})
Hopefully it will help.

Related

SharedFlow in Android project not working as expected

I was trying to pass events from UI to viewModel using sharedFlow
this is my viewmodel class
class MainActivityViewModel () : ViewModel() {
val actions = MutableSharedFlow<Action>()
private val _state = MutableStateFlow<State>(State.Idle)
val state: StateFlow<State> = _state
init {
viewModelScope.launch { handleIntents() }
}
suspend fun handleIntents() {
actions.collect {
when (it) {...}
}
}
}
and this is how i am emiting actions
private fun emitActions(action: Action) {
lifecycleScope.launch {
vm.actions.emit(action)
}
}
For the first time emission happening as expected, but then it is not emitting/collecting from the viewmodel.
Am i doing anything wrong here??
When I used collectLatest() instead of collect() it worked as expected
collectLatest() instead of collect() hides the problem
when you do launch{ collect() } the collect will suspend whatever it is in launch code block
so if you do
launch{
events.collect {
otherEvent.collect() //this will suspend the launched block indefinetly
} }
solution is to wrap every collect in it's own launch{} code block, collectLatest will just cancel the suspend if new event is emitted

How to send an object inside a Coroutine Kotlin?

I'm starting to use coroutines with Kotlin, I want to pass as a parameter an object from the fragment, but I still haven't understood well how this object could happen to this coroutine, I'll really be grateful for your help
val addObject: LiveData<Object> = liveData {
emit(Result.loading())
try {
emit(Result.success(fetchUser()))
} catch(ioException: Exception) {
emit(Result.error(ioException))
}
}
From my fragment I would have to send my object and then be able to observe it
productViewModel.addObject(Object).observe
you cant pass an object in a parameter that does not even make sense.
what you probably want to be doing is something like this
ViewModel
private val _liveData: MutableLiveData<Object> = MutableLiveData()
val liveData:LiveData<Object>
get() = _liveData
fun addObject(myObject:Object){
// do work here
emit(someData)
}
Or even just
fun addObject(myObject:Object):LiveData<Object>{
val liveData: MutableLiveData<Object> = MutableLiveData()
viewModelScope.launch {
//do work then emit back
liveData.postValue(it)
}
return liveData
}
Activity
viewModel.liveData.observe(this, Observer{
})
viewModel.addObject(myObject)
Or for the second example
viewModel.addObject(myObject).observe(this, Observer{
})

Kotlin coroutine stops launching on every fragment instantiation except the first, using ViewPager

I use a coroutine to launch suspended fun emitList
class MyViewModel : ViewModel() {
private var _list = MutableLiveData<ArrayList<ListItem>>()
val _list: LiveData<ArrayList<ListItem>>
get() = _list
fun refresh() = viewModelScope.launch {
emitList()
}
private suspend fun emitList() = runIfBounded {
withContext(Dispatchers.IO) {
// run some database access code
// post results to _list
}
}
}
coroutine launches, but when the fragment the vm is used for is destroyed and recreated by the ViewPager, the coroutine no longer launches.
The idea is to post a list of items generated from a database query every time the fragment is instantiated. But for some reason, the coroutine only launches for the first instantiated fragment and not any fragment thereafter.
Here in fragment's onResume I call refresh
override fun onResume() {
super.onResume()
viewModel.refresh()
}
I'm just confused. Any advice? Thanks.
Fix was to use
GlobalScope.launch { }
not
viewModelScope.launch { }

How to remove liveData forever observe in viewModel

Using liveData in viewModel, I observe if any web api response return, but how to remove specific observe with removeObserve method?
class MyViewModel: ViewModel() {
fun buttomSubmit() {
val responseLiveData = webFetch()
responseLiveData.observeForever(
Observe { // define a Observe?
doSomething()
}
)
}
override fun onCleared() {
responseLiveData.removeObserver(observer) // how to correctly remove the observe
super.onCleared()
}
}
First, define your observer and store it
val mObserver: Observer<MyClass> = Observer { obj ->
doSomething(obj)
}
then you can start observing forever with
responseLiveData.observeForever(mObserver)
and then stop
responseLiveData.removeObserver(mObserver)

Wait For Data Inside a Listener in a Coroutine

I have a coroutine I'd like to fire up at android startup during the splash page. I'd like to wait for the data to come back before I start the next activity. What is the best way to do this? Currently our android is using experimental coroutines 0.26.0...can't change this just yet.
UPDATED: We are now using the latest coroutines and no longer experimental
onResume() {
loadData()
}
fun loadData() = GlobalScope.launch {
val job = GlobalScope.async {
startLibraryCall()
}
// TODO await on success
job.await()
startActivity(startnewIntent)
}
fun startLibraryCall() {
val thirdPartyLib() = ThirdPartyLibrary()
thirdPartyLib.setOnDataListener() {
///psuedocode for success/ fail listeners
onSuccess -> ///TODO return data
onFail -> /// TODO return other data
}
}
The first point is that I would change your loadData function into a suspending function instead of using launch. It's better to have the option to define at call site how you want to proceed with the execution. For example when implementing a test you may want to call your coroutine inside a runBlocking. You should also implement structured concurrency properly instead of relying on GlobalScope.
On the other side of the problem I would implement an extension function on the ThirdPartyLibrary that turns its async calls into a suspending function. This way you will ensure that the calling coroutine actually waits for the Library call to have some value in it.
Since we made loadData a suspending function we can now ensure that it will only start the new activity when the ThirdPartyLibrary call finishes.
import kotlinx.coroutines.*
import kotlin.coroutines.*
class InitialActivity : AppCompatActivity(), CoroutineScope {
private lateinit var masterJob: Job
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + masterJob
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
masterJob = Job()
}
override fun onDestroy() {
super.onDestroy()
masterJob.cancel()
}
override fun onResume() {
this.launch {
val data = ThirdPartyLibrary().suspendLoadData()
// TODO: act on data!
startActivity(startNewIntent)
}
}
}
suspend fun ThirdPartyLibrary.suspendLoadData(): Data = suspendCoroutine { cont ->
setOnDataListener(
onSuccess = { cont.resume(it) },
onFail = { cont.resumeWithException(it) }
)
startLoadingData()
}
You can use LiveData
liveData.value = job.await()
And then add in onCreate() for example
liveData.observe(currentActivity, observer)
In observer just wait until value not null and then start your new activity
Observer { result ->
result?.let {
startActivity(newActivityIntent)
}
}

Categories

Resources