How can I have my viewmodel call a function in my activity or fragment without using a callback and where no data is actually sent. LiveData is used to send data from the viewmodel to the view but I have no data. I just want to notify the ui about something. Should this be done using RxJava or is that overkill?
LiveData is just fine, here is what I did recently (derived from https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150)
first create a class
public class OneTimeEvent {
private Boolean received;
public OneTimeEvent() {
received = false;
}
public Boolean receive () {
if (!received) {
received = true;
return true;
}
return false;
}
}
then in your ViewModel expose your event
private MediatorLiveData<OneTimeEvent> eventListener = new MediatorLiveData<>();
public LiveData<OneTimeEvent> onEvent() {
return eventListener;
}
now you have to trigger the event somewhere in your ViewModel (like something else is finished)
eventListener.setValue(new OneTimeEvent()); //if its a background thread or callback use postValue!
that's it, now you can observe onEvent() in any activity or fragment you like
ViewModel.onEvent().observe(this, new Observer<OneTimeEvent>() {
#Override
public void onChanged(OneTimeEvent oneTimeEvent) {
if (oneTimeEvent.receive()){
// do something on event
}
}
});
Hope this helps, it acts just like an EventListener, only that you can Listen from Multiple Locations simultaneously, and each event will only be fired once, eg if the observer is reattached somewhere else.
Related
I'm using Retrofit with RxJava2 to obtain some data from a Rest API. I want to use a SwipeRefreshLayout to update the view and I'm using a ViewModel to handle the API call, so I want to implement a method in there to refresh the data programmatically.
I want to obtain something like this https://stackoverflow.com/a/34276564/6787552 but instead of having a periodic trigger, I want to do that programmatically when the user pull to refresh.
That's the ViewModel:
public class DashboardViewModel extends ViewModel {
public final Single<Dashboard> dashboard;
public DashboardViewModel() {
dashboard = Api.getDashboard();
refresh();
}
public void refresh() {
// Refresh data
}
}
And in the DashboardFragment:
#Override
public View onCreateView(...) {
...
viewModel.dashboard
.observeOn(AndroidSchedulers.mainThread())
.subscribe(dashboard -> {
binding.setDashboard(dashboard);
binding.swipeRefreshLayout.setRefreshing(false);
});
binding.swipeRefreshLayout.setOnRefreshListener(() -> viewModel.refresh());
...
}
Thank you in advance!
EDIT:
That's what I ended up doing:
public class DashboardViewModel extends ViewModel {
private final BehaviorSubject<Dashboard> dashboard;
public DashboardViewModel() {
dashboard = BehaviorSubject.createDefault(Api.getDashboard());
}
public void refresh() {
// I use a Object because null values are not supported
dashboard.onNext(Api.getDashboard());
}
public Observable<Dashboard> getDashboard(){
return dashboard;
}
}
And then in the DashboardFragment just subscribe to viewModel.getDashbaord()
I'm not 100% sure that I understood what you want to do but if I got the question right, you can do something like this:
put a subject inside the model (probably a BehaviorSubject?)
expose it as an observable to the
view and subscribe to it (instead of subscribing to the single)
in the model, when you
receive a new call to refresh() from the ui, do something like
subject.onNext(Api.getDashboard())
in this way, each call to refresh will cause the emission of a new dashboard, and that will be properly bound by the subscription in the view.
I am currently just using EventBus to transfer data from FirebaseMessagingService onMessageReceived to MainActivity , but this is getting tricky as complexity goes on and what if i get multiple notifications ? on the other hand,
Data transfer is costing 1 extra class and 2 boiler plate function due to EventBus.
So Problem is how to transfer data from FirebaseMessagingService to Activity using Rxjava , and is there any way to convert this whole service to some observables ?
You would still need to Service to receive the notifcations. However, you could use a PublishSubject to publish items like this:
class NotificationsManager {
private static PublishSubject<Notification> notificationPublisher;
public PublishSubject<Notification> getPublisher() {
if (notificationPublisher == null) {
notificationPublisher = PublishSubject.create();
}
return notificationPublisher;
}
public Observable<Notification> getNotificationObservable() {
return getPublisher().asObservable();
}
}
class FirebaseMessagingService {
private PublishSubject<Notification> notificationPublisher;
public void create() {
notificationPublisher = NotificationsManager.getPublisher()
}
public void dataReceived(Notification notification) {
notificationPublisher.onNext(notification)
}
}
class MyActivity {
private Observable<Notification> notificationObservable;
public void onCreate(Bundle bundle) {
notificationObservable = NotificationsManager.getNotificationObservable()
notificationObservable.subscribe(...)
}
}
Edit: expanded the example. Please note this is not the best way to do it, just an example
yes, you can convert Service for using Observables by using PublishSubject . Just return it as observable by
subject.asObservable() and pass it new event from onEvent() method by
subject.onNext().
Use Service binding to bind your Service to Activity, and , whithing binding interface, return references to your subjects as observables.
PublishSubject<String> eventPipe = PublishSubject.create();
Observable<String> pipe = eventPipe.observeOn(Schedulers.computation()).asObservable();
// susbcribe to that source
Subscription s = pipe.subscribe(value -> Log.i(LOG_TAG, "Value received: " + value));
// give next value to source (use it from onEvent())
eventPipe.onNext("123");
// stop receiving events (when you disconnect from Service)
if (s != null && !s.isUnsubscribed()){
s.unsubscribe();
s = null;
}
// we're disconnected, nothing will be printed out
eventPipe.onNext("321");
I need to pass some data between two activities MainActivity and ChildActivity. Button click on MainActivity should open ChildActivity and send event with data. I have singleton:
Subject<Object, Object> subject = new SerializedSubject<>(PublishSubject.create());
and in MainActivity I have the following button click handler:
public void onClick(){
startActivity(new Intent(MainActivity.this, ChildActivity.class));
subject.onNext(new SomeEvent(data));
}
and event listener subscription in ChildActivity :
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addEventListeners();
}
private void addEventListeners() {
subject.ofType(SomeEvent.class)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io()).subscribe(
event -> {
loadData(event.getData());
});
}
When I send event after starting activity and call addEventListeners in ChildActivity onCreate is still not subscribed to this event and loadData() is not called.
What is proper way to pass data between activities using RxJava (if it's possible)?
if anybody needs a complete solution to send data between activities using RxJava2
1- Create the bus:
public final class RxBus {
private static final BehaviorSubject<Object> behaviorSubject
= BehaviorSubject.create();
public static BehaviorSubject<Object> getSubject() {
return behaviorSubject;
}
}
2- the sender activity
//the data to be based
MyData data =getMyData();
RxBus.getSubject().onNext(data) ;
startActivity(new Intent(MainActivity.this, AnotherAct.class));
3-the receiver activity
disposable = RxBus.getSubject().
subscribeWith(new DisposableObserver<Object>() {
#Override
public void onNext(Object o) {
if (o instanceof MyData) {
Log.d("tag", (MyData)o.getData();
}
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
});
4-unSubscribe to avoid memory leacks:
#Override
protected void onDestroy() {
super.onDestroy();
disposable.dispose();
}
Reason:
Problem is that you are using PublishSubject. As per documentation of PublishSubject emits all the subsequent items of the source Observable at the time of the subscription. So in your case it will emit event only if it is subscribed.
Fix for your problem
Instead of using PublishSubject use BehaviorSubject which emits the most recently emitted item and all the subsequent items of the source Observable when a observer subscribe to it.
Browse following link for more details.
Im exactly having this behavior
Subscriber OnComplete called twice
(which is is anticipated as per http://reactivex.io/documentation/subject.html)
But in my scenario : it goes something like this :
I have a AudioRecordingService which displays a notification, in which I have options for the user to save or delete the on going recording, which is working perfectly. But I'm trying to get into using RxAndroid, my notification's save button would trigger..
RxEventBus.getInstance().postEvent(new RxEvents(RxEventsEnum.AUDIO_STOP_AND_SAVE));
which triggers
bindUntilActivitySpecificEvent(RxEventBus.getInstance().forEventType(RxEvents.class),ActivityEvent.DESTROY).subscribeOn(
AndroidSchedulers.mainThread()).subscribe(new Action1<RxEvents>() {
#Override public void call(RxEvents rxEvents) {
onEvent(rxEvents);
}
});
and in my onEvent(rxEvent) based on the rxEvents object's data I appropriately save and store recording. The first time I try this, it works fine, but the subsequent times, the
#Override public void call(RxEvents rxEvents) {
onEvent(rxEvents);
}
is being called multiple times, like for example the second time I post an event, this callback is called twice, the third time thrice and so on... (which is actually what PublishSubject does). I don't want this behavior, I want Rx to be a able to post events and receive only the latest event that was posted and nothing else.
Here is my other relevant code
protected final <T> Observable<T> bindUntilActivitySpecificEvent(Observable<T> observable,
ActivityEvent event) {
return observable.compose(RxLifecycle.<T, ActivityEvent>bindUntilEvent(lifecycle(), event))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
and my run of the mill RxEventBus class :
public class RxEventBus {
private static final RxEventBus INSTANCE = new RxEventBus();
public static RxEventBus getInstance() {
return INSTANCE;
}
private RxEventBus() {
}
private final Subject<Object, Object> mBus = new SerializedSubject<>(PublishSubject.create());
public void postEvent(Object event) {
mBus.onNext(event);
}
public <T> Observable<T> forEventType(Class<T> eventType) {
return mBus.ofType(eventType).observeOn(AndroidSchedulers.mainThread());
}
}
What is the best approach using RxAndroid ? Please note that I am looking for RxAndroid solution only.
You are creating a new observable every time you trigger an event in
RxEventBus.getInstance().forEventType(RxEvents.class)
You need to cache the observables you create for each event type.
I'm start learning RxJava and I like it so far. I have a fragment that communicate with an activity on button click (to replace the current fragment with a new fragment). Google recommends interface for fragments to communicate up to the activity but it's too verbose, I tried to use broadcast receiver which works generally but it had drawbacks.
Since I'm learning RxJava I wonder if it's a good option to communicate from fragments to activities (or fragment to fragment)?. If so, whats the best way to use RxJava for this type of communication?. Do I need to make event bus like this one and if that's the case should I make a single instance of the bus and use it globally (with subjects)?
Yes and it's pretty amazing after you learn how to do it. Consider the following singleton class:
public class UsernameModel {
private static UsernameModel instance;
private PublishSubject<String> subject = PublishSubject.create();
public static UsernameModel instanceOf() {
if (instance == null) {
instance = new UsernameModel();
}
return instance;
}
/**
* Pass a String down to event listeners.
*/
public void setString(String string) {
subject.onNext(string);
}
/**
* Subscribe to this Observable. On event, do something e.g. replace a fragment
*/
public Observable<String> getStringObservable() {
return subject;
}
}
In your Activity be ready to receive events (e.g. have it in the onCreate):
UsernameModel usernameModel = UsernameModel.instanceOf();
//be sure to unsubscribe somewhere when activity is "dying" e.g. onDestroy
subscription = usernameModel.getStringObservable()
.subscribe(s -> {
// Do on new string event e.g. replace fragment here
}, throwable -> {
// Normally no error will happen here based on this example.
});
In you Fragment pass down the event when it occurs:
UsernameModel.instanceOf().setString("Nick");
Your activity then will do something.
Tip 1: Change the String with any object type you like.
Tip 2: It works also great if you have Dependency injection.
Update:
I wrote a more lengthy article
Currently I think my preferred approach to this question is this to:
1.) Instead of one global bus that handles everything throughout the app (and consequently gets quite unwieldy) use "local" buses for clearly defined purposes and only plug them in where you need them.
For example you might have:
One bus for sending data between your Activitys and your ApiService.
One bus for communicating between several Fragments in an Activity.
One bus that sends the currently selected app theme color to all Activitys so that they can tint all icons accordingly.
2.) Use Dagger (or maybe AndroidAnnotations if you prefer that) to make the wiring-everything-together a bit less painful (and to also avoid lots of static instances). This also makes it easier to, e. g. have a single component that deals only with storing and reading the login status in the SharedPreferences - this component could then also be wired directly to your ApiService to provide the session token for all requests.
3.) Feel free to use Subjects internally but "cast" them to Observable before handing them out to the public by calling return subject.asObservable(). This prevents other classes from pushing values into the Subject where they shouldn't be allowed to.
Define events
public class Trigger {
public Trigger() {
}
public static class Increment {
}
public static class Decrement {
}
public static class Reset {
}
}
Event controller
public class RxTrigger {
private PublishSubject<Object> mRxTrigger = PublishSubject.create();
public RxTrigger() {
// required
}
public void send(Object o) {
mRxTrigger.onNext(o);
}
public Observable<Object> toObservable() {
return mRxTrigger;
}
// check for available events
public boolean hasObservers() {
return mRxTrigger.hasObservers();
}
}
Application.class
public class App extends Application {
private RxTrigger rxTrigger;
public App getApp() {
return (App) getApplicationContext();
}
#Override
public void onCreate() {
super.onCreate();
rxTrigger = new RxTrigger();
}
public RxTrigger reactiveTrigger() {
return rxTrigger;
}
}
Register event listener wherever required
MyApplication mApp = (App) getApplicationContext();
mApp
.reactiveTrigger() // singleton object of trigger
.toObservable()
.subscribeOn(Schedulers.io()) // push to io thread
.observeOn(AndroidSchedulers.mainThread()) // listen calls on main thread
.subscribe(object -> { //receive events here
if (object instanceof Trigger.Increment) {
fabCounter.setText(String.valueOf(Integer.parseInt(fabCounter.getText().toString()) + 1));
} else if (object instanceof Trigger.Decrement) {
if (Integer.parseInt(fabCounter.getText().toString()) != 0)
fabCounter.setText(String.valueOf(Integer.parseInt(fabCounter.getText().toString()) - 1));
} else if (object instanceof Trigger.Reset) {
fabCounter.setText("0");
}
});
Send/Fire event
MyApplication mApp = (App) getApplicationContext();
//increment
mApp
.reactiveTrigger()
.send(new Trigger.Increment());
//decrement
mApp
.reactiveTrigger()
.send(new Trigger.Decrement());
Full implementation for above library with example -> RxTrigger