In the project, there are many modules. Each module may want to receive data emitted from other module/modules.
The problem trying to solve is, when we subscribe an observable A, this observable may have not been created yet. To solve this problem, two approaches are discussed:
Have a Observable Registry singleton. Every time when an observable is ready, put a pair into the Registry, and notify all modules that Registry changed. Each module will listen to this notification, and decide if it is a change it interested. If it is notified that the observable is ready, subscribe it. If the observable is removed, update the registry, delete corresponding pair, and notify modules, module unsubscribe it.
Rx implemented event bus. Take use of Subject: create a singleton Subject, then whenever an event or data or item need to be emitted, emit it from this subject. In your subscriber, you only subscribe on specific event, by using a filter on the subject.
The first approach looks very straightforward, and I cannot tell why it's not good. But it just doesn't look scalable to me. The second approach is the recommended way to replace otto/eventbus with Rx.
Any thoughts on these approaches?
For me the first approach is not that simple and too much to handle. The subscribers can subscribe to a topic and start receiving events whenever there is one for that topic and they would not know if there is a new event source as they don't care.
I use extended variant of version 2, which is RxHub library. It is quite flexible as allows different behavior per topic/tag.
Related
In the past, I was using LocalBroadcastManager and EventBus in my chat and taxicab applications which are now either deprecated or not recommended using them.
I intend to replace them with new data structures like mutablesharedflow or channel,
i want to know which one is better matched with my case? or maybe another data structure?
From Roman Elizarov, Channels were added as an inter-coroutine communication primitive.
You cannot use channels to distribute events or state updates in a way that allows multiple subscribers to independently receive and react upon them.
So they introduced Flow. But Flow is a cold observable, where every subscriber gets their own data (independent from other subscribers). With SharedFlow you get a hot observable that emits independently of having any subscriber(s).
You could do the same with ConflatedBroadcastChannel. But JetBrains recommend to use Flow in favor of Channels because of their simpler API.
So if you want to migrate to Coroutines and you need a hot observable multiple subscribers can listen to, you should go with SharedFlow.
Both are good for listening one time events. Everything depends in your use cases.
SharedFlow known as hot flow ->
Emit events even if no observer is listening to it
If no observer is listening to it, you loose these events
Channels known as cold flow
Does not emit events when no observer is not listening to it. It works like a BlockingQueue.
When you start to collect, it collects all data which were sent.
When using Firebase Cloud Messaging on Android, it is often desirable to notify the current Activity of an incoming push notification. One of the recommended ways to do this has been to use LocalBroadcastManager to send an Intent from the FirebaseMessagingService implementation to the Activity (StackOverflow example answer).
However, as of version 1.1.0-alpha01 (2018-12-17), LocalBroadcastManager is deprecated:
LocalBroadcastManager is an application-wide event bus and embraces layer violations in your app: any component may listen events from any other. You can replace usage of LocalBroadcastManager with other implementation of observable pattern, depending on your usecase suitable options may be LiveData or reactive streams.
While it is highly likely that this class will remain available for a while longer, I would like to start cleaning up our applications anyway, so I want to migrate to something better before Google actually removes the old way.
Right now, there are two main roles that these local broadcasts have in our apps:
Update the UI with the new data from the push notification. The way this worked was that each Activity that cares about the incoming push data has a broadcast receiver that listens for the appropriate message and updates its own view data.
Force the user to log out if the server sends a notification to end the session. This works with each activity having an instance of a broadcast receiver that listens for a logout event, ends the Activity, and starts the Login Activity.
As I see it, these use-cases have issues with both of their suggested alternatives:
LiveData is easiest to use in an Activity or Fragment as part of a ViewModel. However, ViewModel is only meant to be used from those classes that directly deal with the UI. Accessing the ViewModel from within the FirebaseMessagingService takes an ugly hack and is a really bad idea from an architectural perspective. Also, different activities and fragments have different ViewModel objects, and I don't want the service to need to access them all.
I can create a Kotlin object (a.k.a. Singleton) with a bunch of LiveData properties, have the FirebaseMessagingService update those LiveData objects from the incoming messages, and have the Activity observe those changes and copy them into its own ViewModel's LiveData properties. The problem with that is twofold: first, it requires me to have two identical LiveData objects for each piece of data, one in the ViewModel and one in the object; and second, it doesn't help me with handling the "log out event", because LiveData is meant to handle changing data, not listening to a stream of events. (I may be able to handle the second issue using this LiveData Event Wrapper, but that still feels like a bad hack over something that isn't meant to work this way.)
While reactive streams, such as RxJava, will probably do what I need, I already forced my team to learn Kotlin, Android Databinding, Android ViewModel, and a bunch of other new stuff in the last few months, and I don't think they can take much more. RxJava is also a large thing to add for just this one use, and we have no plans to rewrite the entire application to take advantage of it in order to justify its addition.
One suggestion I found was to use Kotlin Coroutines with Channels or Flows. These can be used very similar to reactive streams, but (unlike RxJava) are intended to be used with Kotlin and benefit from Kotlin's improvements over Java. This option is especially attractive now that Google has announced that they are focusing on Kotlin for Android development instead of Java.
While this seems to me to be the best option, I have not managed to find any feedback from others about whether it works and if there are side-effects and/or pitfalls to such an implementation. The only thing I found was an open issue on the kotlinx.coroutines repository about the need for providing an example of an application like this. While I'd love to contribute such an example, I don't think I know enough about it to create a good example, and I don't want my production apps to be the guinea pig. I also don't know whether it is better (or proper) to use explicit couroutines with Channel or to use suspend with Flow for this case.
In summary:
Are Kotlin Coroutines and their associated concurrency structures a good way to handle communication between Android Service and Activity?
If yes, which Kotlin type makes more sense to use, Channel or Flow?
Coroutines don't really help with the handoff of data from one software component to another. They help with the processing multiple units of asynchronous work using syntax that appears as if they were synchronous. That's the bottom line for coroutines. They're analogous to async/await syntax in JavaScript. While you might use a coroutine to access data from asynchronous sources, it doesn't give you any primitves to proxy that data on to other components.
LiveData probably works just fine for what you're trying to do. Don't conflate ViewModel with LiveData - they solve different problems. While you're correct that ViewModel should only be accessed by code that deals with UI, that guideline doesn't extend to LiveData. It's perfectly reasonable to expose a LiveData that reflects current data from FirebaseMessagingService that is later picked up by a ViewModel, transformed, and passed on to a view. This LiveData could be a singleton, or obtained via whatever dependency injection infrastructure you choose.
Bear in mind that LiveData is really only supposed to be used for managing changes in state. It's not a "stream" of data that your app can listen to. You will need to make sure that your infrastructure is state-based in order for this to work out well. FCM itself is not state-based, but if you want your views to respond to messages from FCM, you'll need to retain enough context between each message to make sure your UI responds consistently to new messages (or the lack of messages altogether).
I have previously used MediatorLiveData to combine results from FCM, Room and Networking in my Repository. It worked well. However, now I want more complexity and some additional bonuses from RxJava. This is why I have decided to use RxJava this time. I can combine Room and Networking with Observable.concatArrayEager(my _observables). However, I don't now how to do that after FCM pushes value, and how should I notify my main observable after new changes occur? No examples on this issue whatsoever. It is crucial part. I receive FCM in my BroadCastReceiver and then notified my repository's livedata, which notified my MediatorLiveData... How to do that with RxJava? Would really welcome any advice, because it is really important issue.
IMHO, you don't need to combine data from different sources.
Using the database as your single source of truth is definitely easier.
Room supports Flowable like:
#Query(“SELECT * FROM Users WHERE id = :userId”)
Flowable<User> getUserById(String userId);
Each time there is any updates to the table Users, Room will emit the updated user automatically once you subscribed.
The following approach may help you:
In you Activity / Fragment, subscribe to the Flowable.
Once receiving FCM notification, call network api and update the db.
Do with the updated data.
Anyway, if you need to combine sets of items from different Observables, you can use zip.
The concat operator emit the observables in the order specified, that means the second observable won't begin emitting till the first observable finish emitting objects.
But with merge operator you can combine as many observable you want into a single observable. for example:
Observable.merge(dbObservable, webObservable, fcmObservable)
.subscribe(item -> System.out.println(item));
P.S: In case you want to fetch data from a data source and keep it up-to-date using fcm you can do this:
fcmObservable
.startWith(dbObservable)
.subscribe(item -> System.out.println(item));
These are just few pattern available, as always there is nothing impossible in RxJava, based on what you want to do there is always a better RxChain for the job!
I've seen patterns like this:
Observable<String> nameChanges = nameDataSource.changes().share();
// One subscriber
autoUnsubscribe(nameChanges.subscribe(() -> { ... }));
// Another subscriber
autoUnsubscribe(nameChanges.map(...).filter(...).subscribe(...));
// autoUnsubscribe is called when the UI is torn down
My question is:
Why is it necessary to call share() whenever I want to listen to the Observable in multiple places?
Why is not share() the default behavior for all observables?
It would be nice if the code above worked the same even without .share(). I would not have to think about when I need to share an Observable, and when I don't.
Is it for performance reasons because having a single subscriber is a special case that can be handled more efficiently?
From the docs this is not clear to me:
share() returns a new ObservableSource that multicasts (shares) the original ObservableSource. As long there is at least one Observer this ObservableSource will be subscribed and emitting data.
There are two kinds of Observables: cold and hot. Cold Observables start producing items when there is an Observer to them and they do it on an individual basis. Subscribing with multiple Observers will result in multiple runs of the same cold Observable. An example of this is an Observable that issues a network request.
In contrast, a hot Observable can produce items with or without the presence of Observers. These Observables usually multicast the same items to all of their current Observers. An example of this is an Observable of button clicks.
To turn a cold Observable into a hot one, you can use publish() which will make sure only a single subscription is established to the source Observable no matter how many Observers there are. Thus, the chain will now act as a hot multicasting Observable from the Observers' perspective.
However, often it is not economic to keep an originally cold Observable running after all Observers of publish() have gone away, therefore the refCount() operator can be used to keep track of them and stop the source when there are no interesting parties left.
If your source is already hot, you don't need share().
Why is not share() the default behavior for all observables?
Actually, this property is one of the important contributions of the modern reactive programming paradigm: the distinction of the hot and cold Observables.
However, multicasting is expensive on itself because you have to keep track of the set of current Observers in order to notify them with new events, and this can lead to all sorts of logical race conditions to be defended against. A cold Observable is known to talk only to a single Observer so there is no need for the extra tracking overhead.
I would not have to think about when I need to share an Observable, and when I don't. I'm afraid that you will have to think when programming. ;)
In the above code, yes, sharing makes sense. But what if you we're updating something on the backend, using the same network and method call from two different points, updating with different data? You would have the same call with the same Observable, but you certainly wouldn't want to share the Observable instance (and the network call) because that means that all successive calls with data would be discarded in the favour of the first one. Or if you wanted to start a timer whenever you're subscribed, you again wouldn't want to share this with other Observables.
Also, it is much easier to add certain behaviour than to remove it. For example, if sharing was the default, and the second subscriber wouldn't want to share and removes the sharing - what about the third one? Will it share it with the first one or not? At that point the sharing property becomes the property of the Subscriber and not the Observable and you would have no clear way of knowing which one is shared and which one isn't.
I have been using Event Bus in my apps (i.e: greenrobot/EventBus). But I find some disadvantages in using Event Bus:
Chaining tasks execution is difficult
A lot of classes to represent events
Less clear code (well, it's still possible to trace, but not as clear)
I have been researching for new techniques to deal with this problem. And I read quite a bit about RxJava and wonder if it could be a solution.
So my questions about RxJava (based on what I have read recently):
Could RxJava observer be registered at any time? So not just when creating the Observable. With EventBus this is possible, I could subscribe at any time, not just when the Observable is created.
How do you handle two or more publishers publishing the same type of event (e.g: navigation event)?
Tightly coupling the publisher(s) and the subscriber, means that I have to explicitly specify the publisher(s) every time. So I have to worry not just with the type of events, but also the originators. With EventBus, I only need to worry about the type of events and not the originators.
1) Once you have an instance of an Observable, you can subscribe to it at any time and from any thread, even concurrently.
2) We usually merge the streams of multiple observables via Observable.merge() or use a serialized PublishSubject.
3) If you observe an Observable, there could be dozens of upstream operators and sources involved, but you'll get a sequential stream of values no matter what. You only need to get a hold onto an Observable representing some source of events and the observer doesn't need to know if the event was merged, filtered, made a roundtrip over the network and got delayed before arriving in your onNext() method. You can naturally implement or use some lookup service that will get you an Observable to reduce the coupling, but with RxJava, coupling is not usually an issue.