Android Kotlin - How to use flow.CollectLatest{ } in a loop - android

I need to use flow.collectLatest {} in my fragment in OnViewCreated, and then in a loop under some condition multiple times (I made a filter, so each time different data should be retrieved).
This is my code for calling collectLatest:
viewLifecycleOwner.lifecycleScope.launch {
myViewModel.myFlow.collectLatest { pagingData ->
myAdapter.submitData(pagingData)
myAdapter.notifyDataSetChanged()
}
}
I use this block of code in both onViewCreated and in the loop.
However, it gets called only once, in OnViewCreated.
In the loop, sometimes it gets called, and then it needs 2-3min to retrieve data, but most of the time nothing changes.
I guess it could be an issue related to needing much more time to retrieve data, or it just shouldn't be used this way.
Some of the possible solutions I tried, but didn't work:
using delay
adding flowOn(Despatchers.IO) in the end of the flow
switching flow call to a different thread

You don't need a loop for this, should only collect it on onViewCreated() once time. You should have two flows(one for your filter and another for your data) and to use switcMap(), and your adapter/view should call viewModel to notify any change that to be done.
Here an example:
//you can use any object type for your filter, in this example i used a sealed class
private val _transactionFilter = MutableLiveData<TransactionFilter>(
TransactionFilter.TransactionsByDate(Date())
)
val transactions: LiveData<Data> = _transactionFilter.switchMap { filter ->
//code to return data
}
Kotlin Flow's have a switchMap() too, i used liveData because flow.switchMap was experimental yet.
Another thing: you don't need call notifiyDataSetChanged() when using ListAdapter

Related

How to get data from a MutableLiveData

I'm trying to get data from a MutableLiveData; however, it seems like something is wrong with the code, can you guys check for me please?
I can get the object, but I failed to add the object to a mutableList
properties = ArrayList()
propertyViewModel.propertyItemLiveData.observe(
viewLifecycleOwner,
Observer { propertyItems ->
for (property in propertyItems){
var p:Property = Property(property.id,property.address
,property.price,property.phone,property.lat,property.lon)
println(p)// i can display data
properties.add(p)//when i add to properties, the properties still null. Why?
}
}
)
if (properties.isEmpty()){
println("null")
}
The code in the observer will only run when propertyItemLiveData pushes a new value, or if it already has a value when you first observe it. But from the docs:
As soon as an app component is in the STARTED state, it receives the most recent value from the LiveData objects it’s observing. This only occurs if the LiveData object to be observed has been set.
So you won't actually get a value until your Activity or Fragment hits the onStart() callback, meaning your observer code won't run until then. If the code you posted is running earlier than that (say in onCreate), then what you're doing is:
creating an empty list
adding an observer that will add stuff to that list (but it won't run until later)
checking if the list is still empty (it definitely is)
Because of the observer pattern, where your code reacts to new data/events being pushed to it, whatever you need to do with that populated list should be part of the observer code. It should react to the new value and take action - update a list view, alert the user, start an API call, whatever
propertyViewModel.propertyItemLiveData.observe(viewLifecycleOwner) { propertyItems ->
// handle the propertyItems, add them to your list etc
// then do whatever needs to happen with the list, e.g. display it
updateDisplay(propertyList)
}
btw if Property is a data class and you're just copying all its data, you can add to your list like this:
properties.addAll(propertyItems.map { it.copy() })
// or propertyItems.map(Property::copy)
hello first of all in kotlin in general you have to use mutableList and the check of empty or any other instruction should inside the call back like this :
properties = mutableListOf<YourClass>()
propertyViewModel.propertyItemLiveData.observe(
viewLifecycleOwner,
Observer { propertyItems ->
for (property in propertyItems){
var p:Property = Property(property.id,property.address
,property.price,property.phone,property.lat,property.lon)
println(p)// i can display data
properties.add(p)//when i add to properties, the properties
}
if (properties.isEmpty()){
println("null")
}
}
)

Use observe for a variable that updated inside another observe in Kotlin

I am trying first handle the response from API by using observe. Later after observing the handled variable I want to save it to database.
The variable tokenFromApi is updated inside tokenResponseFromApi's observer. Is it possible to observe tokenFromApi outside the observer of tokenResponseFromApi? When debugged, the code did not enter inside tokenFromApi observer when the app started.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
var tokenResponseFromApi: LiveData<String>? = MutableLiveData<String>()
var tokenFromApi: LiveData<TokenEntity>? = MutableLiveData<TokenEntity>()
tokenResponseFromApi?.observe(viewLifecycleOwner, Observer {
tokenResponseFromApi ->
if (tokenResponseFromApi != null) {
tokenFromApi = viewModel.convertTokenResponseToEntity(tokenResponseFromApi, dh.asDate)
}
})
tokenFromApi?.observe(viewLifecycleOwner, Observer {
tokenFromApi ->
if (tokenFromApi != null) {
viewModel.saveTokenToDB(repo, tokenFromApi)
}
})
}
Your problem is that you're registering the observer on tokenFromApi during setup, and when you get your API response, you're replacing tokenFromApi without registering an observer on it. So if it ever emits a value, you'll never know about it. The only observer you have registered is the one on the discarded tokenFromApi which is never used by anything
Honestly your setup here isn't how you're supposed to use LiveData. Instead of creating a whole new tokenFromApi for each response, you'd just have a single LiveData that things can observe. When there's a new value (like an API token) you set that on the LiveData, and all the observers see it and react to it. Once that's wired up, it's done and it all works.
The way you're doing it right now, you have a data source that needs to be taken apart, replaced with a new one, and then everything reconnected to it - every time there's a new piece of data, if you see what I mean.
Ideally the Fragment is the UI, so it reacts to events (by observing a data source like a LiveData and pushes UI events to the view model (someone clicked this thing, etc). That API fetching and DB storing really belongs in the VM - and you're already half doing that with those functions in the VM you're calling here, right? The LiveDatas belong in the VM because they're a source of data about what's going on inside the VM, and the rest of the app - they expose info the UI needs to react to. Having the LiveData instances in your fragment and trying to wire them up when something happens is part of your problem
Have a look at the App Architecture guide (that's the UI Layer page but it's worth being familiar with the rest), but this is a basic sketch of how I'd do it:
class SomeViewModel ViewModel() {
// private mutable version, public immutable version
private val _tokenFromApi = MutableLiveData<TokenEntity>()
val tokenFromApi: LiveData<TokenEntity> get() = _tokenFromApi
fun callApi() {
// Do your API call here
// Whatever callback/observer function you're using, do this
// with the result:
result?.let { reponse ->
convertTokenResponseToEntity(response, dh.asDate)
}?.let { token ->
saveTokenToDb(repo, token)
_tokenFromApi.setValue(token)
}
}
private fun convertTokenResponseToEntity(response: String, date: Date): TokenEntity? {
// whatever goes on in here
}
private fun saveTokenToDb(repo: Repository, token: TokenEntity) {
// whatever goes on in here too
}
}
so it's basically all contained within the VM - the UI stuff like fragments doesn't need to know anything about API calls, whether something is being stored, how it's being stored. The VM can update one of its exposed LiveData objects when it needs to emit some new data, update some state, or whatever - stuff that's interesting to things outside the VM, not its internal workings. The Fragment just observes whichever one it's interested in, and updates the UI as required.
(I know the callback situation might be more complex than that, like saving to the DB might involve a Flow or something. But the idea is the same - in its callback/result function, push a value to your LiveData as appropriate so observers can receive it. And there's nothing wrong with using LiveData or Flow objects inside the VM, and wiring those up so a new TokenEntity gets pushed to an observer that calls saveTokenToDb, if that kind of pipeline setup makes sense! But keep that stuff private if the outside world doesn't need to know about those intermediate steps

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 e.g: 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

How to get the value of a Flow outside a coroutine?

How can I get the value of a Flow outside a coroutine similarly to LiveData?
// Suspend function 'first' should be called only from a coroutine or another suspend function
flowOf(1).first()
// value is null
flowOf(1).asLiveData().value
// works
MutableLiveData(1).value
Context
I'm avoiding LiveData in the repository layer in favor of Flow. Yet, I need to set, observe and collect the value for immediate consumption. The later is useful for authentication purpose in a OkHttp3 Interceptor.
You can do this
val flowValue: SomeType
runBlocking(Dispatchers.IO) {
flowValue = myFlow.first()
}
Yes its not exactly what Flow was made for.
But its not always possible to make everything asynchronous and for that matter it may not even always be possible to 'just make a synchronous method'. For instance the current Datastore releases (that are supposed to replace shared preferences on Android) do only expose Flow and nothing else. Which means that you will very easiely get into such a situation, given that none of the Lifecycle methods of Activities or Fragments are coroutines.
If you can help it you should always call coroutines from suspend functions and avoid making runBlocking calls. A lot of the time it works like this. But it´s not a surefire way that works all the time. You can introduce deadlocks with runBlocking.
Well... what you're looking for isn't really what Flow is for. Flow is just a stream. It is not a value holder, so there is nothing for you retrieve.
So, there are two major avenues to go down, depending on what your interceptor needs.
Perhaps your interceptor can live without the data from the repository. IOW, you'll use the data if it exists, but otherwise the interceptor can continue along. In that case, you can have your repository emit a stream but also maintain a "current value" cache that your interceptor can use. That could be via:
BroadcastChannel
LiveData
a simple property in the repository that you update internally and expose as a val
If your interceptor needs the data, though, then none of those will work directly, because they will all result in the interceptor getting null if the data is not yet ready. What you would need is a call that can block, but perhaps evaluates quickly if the data is ready via some form of cache. The details of that will vary a lot based on the implementation of the repository and what is supplying the Flow in the first place.
You could use something like this:
fun <T> SharedFlow<T>.getValueBlockedOrNull(): T? {
var value: T?
runBlocking(Dispatchers.Default) {
value = when (this#getValueBlockedOrNull.replayCache.isEmpty()) {
true -> null
else -> this#getValueBlockedOrNull.firstOrNull()
}
}
return value
}
You can use MutableStateFlow and MutableSharedFlow for emitting the data from coroutine and receiving the data inside Activity/Fragment. MutableStateFlow can be used for state management. It requires default value when initialised. Whereas MutableSharedFlow does not need any default value.
But, if you don't want to receive stream of data, (i.e) your API call sends data only once, you can use suspend function inside coroutine scope and the function will perform the task and return the result like synchronous function call.
To get the value of a Flow outside of a coroutine, the best option is to create the flow as a StateFlow and then call the value property on the StateFlow.
class MyClass {
private val mutableProperty = MutableStateFlow(1)
val property = mutableProperty.asStateFlow()
...
mutableProperty.value = 2
}
...
val readProperty = MyClass().property.value
val propertyAsFlow = MyClass().property as Flow<Int>

Android - observing DB changes within ViewModel

I am using a lot of LiveData in my projects and it's great in those cases where I need to pass something to views since it's intention is to be observed by lifecycle owners (i.e. views).
But I wonder what should I use in those cases when I need to apply some logic in my view models every time when some data from DB changes?
I am familiar with Transformations (map and switch) but (if I am right) they are just a way to transform liveData objects, not a place where I can execute some viewmodel's logic.
If I understand correctly, observing LiveData in viewModels is bad practice.
What is an alternative? Some of the RxJava observable types? Or something else?
"they are just a way to transform liveData objects, not a place where I can execute some viewmodel's logic."
Yes you're right. It's because:
Functions in both map() and switchMap() run on the Main Thread,
so no long running operations should be done there!
But I don't think observing LiveData in ViewModel is bad practice, because in Android we have MediatorLiveData for this purpose. If you take a look at source code of map and switchMap function, you'll see they use MediatorLiveData in there.
So the only problem here is that if some logic you want to execute is a long running task, you must run it in background thread when observe changes from the source LiveData. You can use Rx or something to run it in background thread like below:
private val _downloadState = MutableLiveData<DownloadDataState>()
val downloadState: LiveData<DownloadDataState> = _downloadState
// version observe changes in downloadState
val version = MediatorLiveData<String>().apply {
addSource(downloadState) {
// Whenever value of downloadState changes, this block will run your logic
Single.just(dataRepository.fetchDataVersion())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result ->
// Then set value to the observer
value = result
},
{ e: Throwable ->
e.printStackTrace()
}
)
}
}

Categories

Resources