call part of stream once with multiple subscribers? - android

I have custom Rx Adapter for socket communication.
Outside of it I observe Flowable with messages.
Then I have some manager that handles every message and then emit it further.
fun observeSocket() = socketManager
.observe()
.doOnNext{
insideMessageHandler.handle(it)
}
Then I have two subscribers that does observeSocket().subscribe()
The problem is that with every message insideMessageHandler.handle(it) is called twice. I want to find the way where part of stream will be common for every subscriber. Unfortunately .share() operator at the end of observeSocket() don't work.
I have something like this twice:
/onNextInside
Flowable/-onNextOutsideSubscriber1
Flowable\-onNextOutsideSubscriber2
\-onNextInside
And I want to have something like this:
/-onNextInside
Flowable/-onNextOutsideSubscriber1
\-onNextOutsideSubscriber2
In code it looks like
insideManager.observeSocket().subscribe({do something first})
insideManager.observeSocket().subscribe({do something second})
The problem is that in this case I have onNextInside called twice
Is it even possible?

The problem is the re-creation of the observable:
fun observeSocket() = socketManager
.observe()
.doOnNext{
insideMessageHandler.handle(it)
}
With every call to observeSocket() you create a new chain, so putting the share() there wouldn't make a difference.
Instead define this chain as a shared singleton:
private val _observeSocket = socketManager
.observe()
.doOnNext{
insideMessageHandler.handle(it)
}
.share()
fun observeSocket() = _observeSocket

For this case you have Subjects.
var yourSubject = PublishSubject<Type>.create()
fun observeSocket() = socketManager.doOnNext(yourSubject::onNext)
Then you can observe on your observeSocket() and another/multiple subscriber on the subject.
If you want to share the emitting of one Observables you can use the share() operator which makes sure that even when multiple subscribers are subscribed it only create one instance and share the emitted data using
fun observeSocket() = socketManager
.doOnNext(insideMessageHandler::handle)
.share()
Anyway, it's not a good usecase for reactive stuff to flat/emit to another observables in doOnNext for this purpose.
Better share one Flowable and subscribe multiple times and flatmap the values to your need instead of putting everything in one flowable since if there's an error in the stream the whole stream may not emit any more data.

Related

Send data from repository to view model in MVVM architecture

I'm confused about using MVVM architecture! In some tutorials, LiveData is stored in a repository and then passed to the ViewModel. In many others, LiveData is defined within a function of repository and passed to the ViewModel using the result of the function. But Google says:
It may be tempting to work LiveData objects in your data layer class, but LiveDatais not designed to handle asynchronous streams of data. ........ If you need to use streams of data in other layers of your app, consider using Kotlin Flows and then converting them to LiveData in the ViewModel using asLiveData(). .... For codebases built with Java, consider using Executors in conjuction with callbacks or RxJava.
I prefer to use Kotlin coroutines or Flows. But I do not know if in my case is really needed or not. I'm working on a chat application. When a message is received, a listener is called in the repository and the message data is received in it. (The listener runs in the background thread) Now I want to send the message object to the ViewModel and add it to a LiveData that stores the list of messages.
object ChatsRepo {
fun listen(socket: Socket) {
socket.on(Config.ON_MESSAGE, onMessage)
}
fun sendMessage(socket: Socket, json: String) {
socket.emit(Config.ON_MESSAGE, json)
}
private val onMessage = Emitter.Listener { args: Array<Any> ->
//This message object must be sent to ViewModel
val message = Gson().fromJson(args[0].toString(), Message::class.java)
}
}
I can easily do this using the higher-order function:
object ChatsRepo {
lateinit var listener: (Message) -> Unit
private val onMessage = Emitter.Listener { args: Array<Any> ->
val message = Gson().fromJson(args[0].toString(), Message::class.java)
listener(message)
}
}
But is it better to use Kotlin coroutines or Flows? In some similar cases, a list needs to be sent to the ViewModel.
I can easily do this using the higher-order function.
Right, this is a callback you can use to notify ViewModel about new messages. Kotlin Coroutines help to avoid callbacks and to have a sequential code.
In your case the onMessage is a hot stream of data, we can convert it to a hot Flow using SharedFlow:
private val _messagesFlow = MutableSharedFlow<Message>(extraBufferCapacity = 64)
val messagesFlow: SharedFlow<Message> = _messagesFlow
private val onMessage = Emitter.Listener { args: Array<Any> ->
val message = Gson().fromJson(args[0].toString(), Message::class.java)
messagesFlow.tryEmit(message)
}
In ViewModel if need it is easy to convert it to LiveData using method asLiveData:
ChatsRepo.messagesFlow.asLiveData()
Dependency to use asLiveData() extension function:
def lifecycle_version = "2.4.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
If you are a Rxjava master, I will not recommend you to use flow/livedata. I think flow/livedata is designed for UI Reactive, not for underlying data transform. So you can use rxjava in data repository, and in viewmodel, you can convert it to livedata and use it.
Livedata was never design for reactive streams it was always been for last layer (viewmodel to view), there were/are workaround when livedata was use with retrofit(using calladapter) and room(which google did it).
And now since the rise of coroutines recommended ways is to use kotlin Flows when dealing with business logic but things to be notice:
1- regular flow are not observables but livedata is observable.
2- it's not good to manage UI state with regular flows but you can do it with livedata.
3- livedata is lifecycle aware but flows are not.
4- livedata is not reactive but flows are.
5- livedata gives you the only latest value what it receives but regular flows throws values from bucket one by one
you might have notice i said alot of regular flows not flow cause there are other mechanism related to flows you can manage UI state that are stateFlow and sharedFlow.
Stateflow is the replacement of livedata the downside is you have to code a little bit more to make it lifecycle aware cause it is not prebuilt and stateflow always gives you the last value it persists during the configuration changes or screen switching(fragment navigation).
sharedFlows is good for managing the one time event like toastMsgs, snackbar etc. sharedFlow was actually the replacement of BroadcastChannels.

RxJava design question: retryWhen and concat to achieve desired functionality

Background:
I have an observable that returns mock items and actual items. Mock items are used in ui while actual items are still on the way. Mock items should always arrive first to prevent accidental override of actual items in ui. The premise is that ui layer is dumb, and it does not know which items it is showing nor it's managing them in any way. So the single source of items is this observable. Simplified it looks like this.
val one = Observable.fromCallable { "Mock/cached items" }
val two = Observable.fromCallable { "Live items" }
val result = Observable.concat(listOf(one, two))
.subscribe {
println(it)
}
Now I want to replay this observable, because further downstream it's just one observable of many that are combined together via combineLatest() operator, hence I can't resubscribe to it individually. So, I've added replayWhen(...) after concat() and everything worked properly.
Problem:
Things got tricky when I needed to replay only mock items in some cases vs replaying all items in other cases. Attempt that failed:
val subject1 = PublishSubject.create<Unit>()
val subject2 = PublishSubject.create<Unit>()
val one = Observable.fromCallable { "Mock/cached items" }.repeatWhen { subject1 }
val two = Observable.fromCallable { "Live items" }.repeatWhen { subject2 }
val result = Observable.concat(listOf(one, two))
.subscribe {
println(it)
}
subject1.onNext(Unit)
subject2.onNext(Unit)
Obviously, it prints only mock items while live items aren't called (anytime soon:-)). This is because concat waits for onComplete from first observable, that doesn't happen because of replay operator.
Solutions, and their drawbacks:
Replace concat with merge. This way both observable would work, but there's no guarantee that observable one would fire before the observable two. Is there any way to achieve ordering between observables without concat?
Have replayWhen() after concat, to replay this result observable as a whole, but have something inside .fromCallable{} of observable two to know when to skip it. Not sure what could it be, as a simple flag would be unreliable and dependant on external state that may change sporadically.
Thank you for any other ideas.

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>

Best way to cache data in Android with ViewModel and RxJava2

In my application I'm using RxJava2 and new class from Architecture Components ViewModel. In my case, I need to push SQL clause to ViewModel, which will do some magic and return Observable that will give me the data I need. Everything works fine, but I am not sure if I am using RX in the best way.
My data flow:
ViewModel has PublishSubject on which I am pushing SQL's. ViewModel has also Observable which is created by mapping subject. Also, I used distinctUntilChanged on Subject, to prevent from executing the same query again.
To cache data I used replay(1).autoconnect(1) on Observable, but that approach had a flaw. Sometimes my Subject pushed Sql when Observable wasn't yet connect, and my data never arrived to me. Should I use BehaviourSubject? Or maybe I shouldn't use replay(1).autoconnect(1) in the first place? Or maybe my whole flow is wrong? Example:
val listSubject: Subject<RawSql> = PublishSubject.create()
val sqlListEmitter: Observable<List<T>> =
listSubject
.subscribeOn(Schedulers.computation())
.map { // SOME MAGIC HERE }
.replay(1).autoConnect(1, { compositeDisposable.add(it) })
In your case autoConnect() just waits for the first subscription to connect() to your stream. Since your subject and your stream build an inherent entity, you might not want to wait for it at all and instead connect it directly.
val listSubject: Subject<RawSql> = PublishSubject.create()
val sqlListEmitter: Observable<List<T>> =
listSubject
.observeOn(Schedulers.computation())
.map { // SOME MAGIC HERE }
.replay(1)
.let {
it.connect(compositeDisposable::add)
it.publish()
}
Also you might need to change subscribeOn() to observeOn(). The subject emits on the same thread as the data is pushed to it and does not consider the thread it's subscribed on.

Trigger Observable on Subject's onNext and Share Result

I have a button which when pressed should make the btnSubject's onNext fire and make an API call in an Observable created in my ViewModel like so:
val apiObservable =
btnSubject.flatMap{ apiService.getSomething()
.toResponseOrErrorObservable()
.subscribeOn(Schedulers.io())}
Then I can reuse this observable to create two more, which are then subscribed to from my view allowing me to keep the logic in my ViewModel like so:
val successObservable = apiObservable.filter{ it.isSuccess() }
val failureObservable = apiObservable.filter{ it.isFailure() }
So apiObservable is triggered by the btnSubject.onNext() being called.
The view is then updated because it's listening to the successObservable and failureObservable
Is this possible? Perhaps with a .share() on the apiObservable?
UPDATE
I added the share operator and all observables emitted items when first subscribing. Even the filters didn't stop it... I must be missing something obvious
There might be a few way to do that.
As you have written, using share() operator multiplies output to many Subscribers. However, you have to be careful, that you also have to call connect() to turn cold Observable into hot one. If calling also replay(), you woudln't need to call connect() many times.
(Source)
However, there is more simple solution: use Jake Wharton's library RxReplayingShare. The author of previous blog post suggests it in his next article

Categories

Resources