How to send an object inside a Coroutine Kotlin? - android

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{
})

Related

How to call a function from in ViewModel in viewModelScope?

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.

After updating LiveData my switchMap don't want to trigger

Like in title, I have _token
private val _token = MutableLiveData<String>()
that should update
val userPackages: LiveData<List<Packages>> = Transformations.switchMap(_token) {
packagesLiveData(it)
}
after i use
fun setToken(token: String) {
Log.d(TAG, "setToken: $token")
_token.postValue(token)
}
from this Log.D I know that im getting valid string
i tried
_token.value = token
but nothing changed
I see from this function that im calling inside switchmap
private fun packagesLiveData(string: String): LiveData<List<Packages>> {
Log.d(TAG, "switchMap: $string")
return liveData {
tvRepository
.getUserPackage(string)
.asLiveData()
}
}
that im not getting any change (cos this function not being called at all)
or if i inicialize value of _token to any string then i see it being called twice but with this initialized value and not with the value from _token.value that i confirmed while testing is indeed changing but switchmap can never access
EDIT
It looks like i just didn't observed my liveData but now when i try to do this I'm getting error that i don't have get function for this val
I'm getting my model in my fragment from this
private val tvViewModel: TvViewModel by viewModels()
and this is how my observer looks like
tvViewModel.userPackages.observe(viewLifecycleOwner, {
Log.d(TAG, "setTvObservers: ${it?.get(0)}")
})
Requested ViewModel:
#HiltViewModel
class TvViewModel #Inject constructor(
private val tvRepository: TVRepository
) :
ViewModel() {
companion object {
const val TAG = "TvViewModel"
}
private val _tvPath = MutableLiveData<String>()
private val _token = MutableLiveData<String>()
val tvPath: LiveData<String> = _tvPath
val userPackages: LiveData<List<Packages>> = Transformations.switchMap(_token) {
packagesLiveData(it)
}
fun setTvPath(path: String) {
_tvPath.postValue(path)
}
fun setToken(token: String) {
Log.d(TAG, "setToken: $token")
_token.postValue(token)
}
private fun packagesLiveData(string: String): LiveData<List<Packages>> {
Log.d(TAG, "switchMap: $string")
return liveData(Dispatchers.IO) {
tvRepository
.getUserPackage(string)
.asLiveData()
}
}
}
Are you observing this LiveData, i.e. userPackages somewhere?
Because as mentioned here,
The transformations aren't calculated unless an observer is observing the returned LiveData object.
So make sure you are observing userPackages somewhere, if it still doesn't work do tell, we'll try to find a solution again :)
EDIT:
By looking at the way you were observing the LiveData using viewLifecycleOwner, it seems that you are observing it inside a fragment. So, inside a fragment, you do not get the ViewModel by calling by viewModels(). Instead, you need to use by activityViewModels<TvViewModel>
if it still doesn't work let me know, we'll try to look again :)
LATEST UPDATE
ok so even though this doesn't give any compile-time warnings, but inside your packagesLiveData, the tvRepository.getUserPackage(string).asLiveData() returns a LiveData, and you are using livedata wrapper again around it. What you need to do is something like this:
private fun packagesLiveData(string: String): LiveData<List<Packages>> {
Log.d(TAG, "switchMap: $string")
return tvRepository
.getUserPackage(string)
.asLiveData()
}

Clear retrofit result with MVVM when fragment back

In my ViewModel I have two MutableLiveData for the response of my webservice :
val getFindByCategorySuccess: MutableLiveData<List<DigitalService>> by lazy {
MutableLiveData<List<DigitalService>>()
}
val getFindByCategoryError: MutableLiveData<String> by lazy {
MutableLiveData<String>()
}
and this method for the request :
fun requestFindByCategory(categoryId: String){
viewModelScope.launch {
when (val retrofitPost = digitalServicesRemoteRepository.getFindByCategoryRequest(categoryId)) {
is ApiResult.Success -> getFindByCategorySuccess.postValue(retrofitPost.data)
is ApiResult.Error -> getFindByCategoryError.postValue(retrofitPost.exception)
}
}
}
It's working fine using it in my Fragment class :
viewModel.getFindByCategorySuccess.observe(viewLifecycleOwner, { digitalServices ->
logD("I have a good response from the webservice; luanch an other fragment now!")
})
The problem is if I go to an other fragment in my observable (using findNavController().navigate(action)). If I go back to the previous fragment, I go automatically to the nextFragment because the observable is called again.
So I'm looking for solutions...
Maybe clearing all my viewmodel when I go back to my fragment ?
Maybe clearing only getFindByCategorySuccess and getFindByCategoryError ?
Maybe an other solution? I think my architecture is not good. What do you think about it ?
By default, a livedata will emit to its current state (the value that exist on it) for any new observer that subscribes to it.
Answering your question, you might try the operator distincUntilChanged transformation, which, according to the documentation:
Creates a new LiveData object that does not emit a value until the source LiveData value has been changed. The value is considered changed if equals() yields false.
But, this showcases a problem with your snippet, and a bad practice that is common when using livedata, you shouldn't expose mutable live data to your observers. Instead, you should expose a non-mutable version of them.
In your case, in my opinion, your view model should look like the following:
private val getFindByCategorySuccess by lazy {
MutableLiveData<List<DigitalService>>()
}
private val getFindByCategoryError by lazy {
MutableLiveData<String>()
}
val onFindByCategorySuccess: LiveData<List<DigitalService>
get() = getFindByCategorySuccess.distincUntilChanged()
val onFindCategoryError: LiveData<List<String>
get() = getFindByCategoryrRror.distincUntilChanged()
And your observers would subscribe as follows:
ExampleFragment
fun setupObservers() {
viewModel.onFindByCategorySuccess.observe(viewLifecycleOwner) { // Do stuff }
}
I hope it helps
I found a solution to my problem using this class :
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val mPending = AtomicBoolean(false)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(owner, { t ->
if (mPending.compareAndSet(true, false))
observer.onChanged(t)
})
}
override fun setValue(t: T?) {
mPending.set(true)
super.setValue(t)
}
}
Like this :
var getFindByCategorySuccess: SingleLiveEvent<List<DigitalService>> = SingleLiveEvent()
var getFindByCategoryError: SingleLiveEvent<String> = SingleLiveEvent()

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)

LiveData is observed multiple times inside onClickListener in Android

I have a repository setup like this
class ServerTimeRepo #Inject constructor(private val retrofit: Retrofit){
var liveDataTime = MutableLiveData<TimeResponse>()
fun getServerTime(): LiveData<TimeResponse> {
val serverTimeService:ServerTimeService = retrofit.create(ServerTimeService::class.java)
val obs = serverTimeService.getServerTime()
obs.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).unsubscribeOn(Schedulers.io())
.subscribe(object : Observer<Response<TimeResponse>> {
override fun onComplete() {
}
override fun onSubscribe(d: Disposable) {
}
override fun onNext(t: Response<TimeResponse>) {
val gson = Gson()
val json: String?
val code = t.code()
val cs = code.toString()
if (!cs.equals("200")) {
json = t.errorBody()!!.string()
val userError = gson.fromJson(json, Error::class.java)
} else {
liveDataTime.value = t.body()
}
}
override fun onError(e: Throwable) {
}
})
return liveDataTime
}
}
Then I have a viewmodel calling this repo like this
class ServerTimeViewModel #Inject constructor(private val serverTimeRepo: ServerTimeRepo):ViewModel() {
fun getServerTime(): LiveData<TimeResponse> {
return serverTimeRepo.getServerTime()
}
}
Then I have an activity where I have an onClickListener where I am observing the livedata, like this
tvPWStart.setOnClickListener {
val stlv= serverTimeViewModel.getServerTime()
stlv.observe(this#HomeScreenActivity, Observer {
//this is getting called multiple times??
})
}
I don't know what's wrong in this. Can anyone point me in the right direction? Thanks.
Issue is that every time your ClickListener gets fired, you observe LiveData again and again. So, you can solve that problem by following solution :
Take a MutableLiveData object inside your ViewModel privately & Observe it as LiveData.
class ServerTimeViewModel #Inject constructor(private val serverTimeRepo: ServerTimeRepo):ViewModel() {
private val serverTimeData = MutableLiveData<TimeResponse>() // We make private variable so that UI/View can't modify directly
fun getServerTime() {
serverTimeData.value = serverTimeRepo.getServerTime().value // Rather than returning LiveData, we set value to our local MutableLiveData
}
fun observeServerTime(): LiveData<TimeResponse> {
return serverTimeData //Here we expose our MutableLiveData as LiveData to avoid modification from UI/View
}
}
Now, we observe this LiveData directly outside of ClickListener and we just call API method from button click like below :
//Assuming that this code is inside onCreate() of your Activity/Fragment
//first we observe our LiveData
serverTimeViewModel.observeServerTime().observe(this#HomeScreenActivity, Observer {
//In such case, we won't observe multiple LiveData but one
})
//Then during our ClickListener, we just do API method call without any callback.
tvPWStart.setOnClickListener {
serverTimeViewModel.getServerTime()
}

Categories

Resources