-1

I'm trying to rely on Reactive Programming to share the result of an http call with many subscribers. At the same time I want to be able to perform the call again (refresh).

I start with a cold Observable that perform the http call and then immediately complete.

I want to wrap it to obtain an hot observable that work like this: every subscriber should always receive the last event (if any) when subscribing and every other event until unsubscribed. I should have a way (external to that observable) to trigger a refresh and thus a new event on all the subscribers.

More in detail:

I have a cold Observable for the http request build by retrofit2. For the sake of completeness this is my service interface

@GET("/path")
Observable<MyData> httpCall();

I ask retrofit for a service:

Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl(REST_BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                    .build();
MyService service = retrofit.create(MyServiceInterface.class);

From the service I get the observable:

Observable<MyData> coldObservable = service.httpCall();

This is a cold observable, it will perform the http call every time subscribe() is called on it and then immediately complete.

I want to expose an hot observable, I've read I can do this:

Observable<MyData>hotObservable = coldObservable.publish()
    .autoConnect();

This way the http call is performed at the first subscribe() on it and if I subscribe multiple times all will "connect" to the same coldObservable.

After the call is completed if I call subscribe() again nothing will happen, not even a callback to completed.

Instead I want it to receive the last event.

If the user request it I want to force a refresh (repeat http call). All subscribers should receive the new result / error.

I imagine something like this:

Observable<MyData> theSmartObservable = helperClass.getObservable();

// at some point later
helperClass.triggerRefresh();

The refresh triggered should produce a new event in theSmartObservable.

How do I build such an observable?

I hope I explained myself, if not please tell in comments.

Daniele Segato
  • 12,314
  • 6
  • 62
  • 88
  • You can use a .replay(1) on your hotObservable to ensure that new subscribers always get the last emitted item from the observable. – JohnWowUs Apr 06 '16 at 10:36
  • sweet that answer the first part of the question – Daniele Segato Apr 06 '16 at 12:12
  • As for the second part, if you want to force a refresh simply create another set of observables and subscribe to them (after unsubscribing from your current observables). I don't believe there's any need to do anything fancier. – JohnWowUs Apr 06 '16 at 12:51
  • that's what i can't / don't want to do. there are multiple places using the data but or that could ask for the data. the refresh event is an event on itself – Daniele Segato Apr 06 '16 at 13:01

1 Answers1

0

Subclassing Observable is tricky. Using composition with a subject as a "bridge" should be easier. Something like

public class HelperClass {

    private Subscription bridgeSubscription;
    private ReplaySubject<MyData> bridgeSubject;
    private MyService service;
    private Subscriber<MyData> mSubscriber = new Subscriber<MyData>() {
                @Override
                public void onCompleted() {
                  // To DO
                }

                @Override
                public void onError(Throwable e) {
                  // TO DO
                }

                @Override
                public void onNext(MyData d) {
                    bridgeSubject.onNext(d);
                }
            };
    public HelperClass(MyService service) {
        this.service = service;
        bridgeSubject = ReplaySubject.create(1);
        bridgeSubscription = this.service.httpCall().publish().autoConnect().subscribe(mSubscriber);
    }

    public Observable<MyData> getObservable() {
        // Might not work too well to hide the observable identity  
        // return bridgeSubject.asObservable();
        return bridgeSubject;
    }

    public void triggerRefresh() {
        if (bridgeSubscription != null) {
            bridgeSubscription.unsubscribe();
            bridgeSubscription = null;
        }
        bridgeSubscription = service.httpCall().publish().autoConnect().subscribe(mSubscriber);
    }
}
JohnWowUs
  • 3,053
  • 1
  • 13
  • 20
  • As I mentioned I'm still learning Rx, first time I read about subjects. I did some research, someone say "don't use them" some say "yeah, better then creating an observable". And some say (and I'm inclined to listen this category) learn when you should use one and when you shouldn't. I'll get back to this when I have a better knowledge. – Daniele Segato Apr 11 '16 at 14:57
  • Given your requirements, I doubt you can achieve your desired behaviour without using Subjects. If you want to learn more, Dávid Karnok (one of the main contributors to RxJava) has a series of blog posts about subjects starting [here](http://akarnokd.blogspot.co.uk/2015/06/subjects-part-1.html). – JohnWowUs Apr 12 '16 at 07:08
  • I tried your code. I does not work how I want it to work. When I call triggerReresh the http call is performed again but none of the observable subscribed to that bridgeSubject receive events. – Daniele Segato Apr 20 '16 at 11:29
  • I've made an edit that returns the bridgeSubject directly instead of trying to hide its identity as a Subject. – JohnWowUs Apr 20 '16 at 11:42
  • Do the subscribers receive the events before you call triggerRefresh? – JohnWowUs Apr 20 '16 at 12:37
  • Yes, and new subscribers receive it too, but old subscribers stop receiving anything after the first event. In fact if i check the subscription it say it's unsubscribed. As for the contract, after an onComplete no more events should be sent – Daniele Segato Apr 20 '16 at 12:41
  • I think the unsubscribe is causing the problems because the subject is directly subscribed to the underlying observable. I've edited the answer to call the subject OnNext manually. – JohnWowUs Apr 20 '16 at 13:15
  • I tried your new version with the Subscriber, still doesn't work. I don't know exactly why but after calling refresh nothing is received in the observers. I've asked another question and I think the solution is RxRelay – Daniele Segato Apr 20 '16 at 13:48
  • Yes. I hadn't realized that the cold observable was calling oncomplete. I assumed you were using a retrofit for your cold observable (I don't think it ever calls oncomplete). – JohnWowUs Apr 20 '16 at 13:55
  • I am using retrofit 2. And it DOES call onComplete after every call :) – Daniele Segato Apr 20 '16 at 13:56