简体   繁体   中英

RxJava. Initial onNext when subscription starts?

I'm trying to implement a class that emits changes using an Observable. When a subscription is done to this observable I want to send an starting/initialization event. Then I want to send the usual events.

For example. Lets say I have two different subscribers A and B. A and B starts subscribing at different times. If MyClass.getChanges() emits event no. 1,2,3,4 and 5.

If A starts it subscription between event 1,2 then it should receive the following events: InitialEvent, 2, 3, 4, 5.

If B starts it subscription between event 4 and 5, then B should receive the following events: InitialEvent, 5.

How to do this using RxJava?

Thanks!

Edit 1

I think I need to explain that the "InitialEvent" is different each time it's emitted. It's calculated by MyClass each time a new subscriber starts to subscribe from getChanged().

My scenario is that MyClass contains a list. The "initialEvent" contains the list at the moment when the subscription is done. Then each change to this list is emitted from getChanges().

What you're looking for is PublishSubject . Subjects are hot Observables , in that they do not wait for Observers to subscribe to them before beginning to emit their items. Here's a bit of info on Subjects .

Here's a quick demo of your use-case

    PublishSubject<String> subject = PublishSubject.create();
    Observable<String> InitEvent = Observable.just("init");
    Observable<String> A = subject.asObservable();
    Observable<String> B = subject.asObservable();

    subject.onNext("1");

    A.startWith(InitEvent)
            .subscribe(s -> System.out.println("A: " + s));

    subject.onNext("2");
    subject.onNext("3");
    subject.onNext("4");

    B.startWith(InitEvent)
            .subscribe(s -> System.out.println("B: " + s));

    subject.onNext("5");

Possibly not really elegant way how about just using a flag? It looks like you just want to replace the first emitted event.

eg for one subscription the following logic:

boolean firstTimeA = true;

myCustomObservable.subscribe(s -> {
   System.out.println(firstTimeA ? "initEvent" : s.toString());
   if(firstTimeA) firstTimeA = false;
});

And since you want to have a second subscription just create a firstTimeB and update it your B subscription.

If I understand what you are asking something like this should work for you

int last = 0;
Observable obs;
List<Integer> list = new ArrayList<>();

public SimpleListObservable() {
    obs = Observable.create(new Observable.OnSubscribe<Integer>() {
        @Override
        public void call(Subscriber<? super Integer> subscriber) {
            while(last < 30) {
                last++;
                list.add(last);
                subscriber.onNext(last);
            }
            subscriber.onCompleted();
        }
    });
}

public Observable<Integer> process() {
    return Observable.from(list).concatWith(obs);
}

As the source observable collects values they are added to the List (you can transform the items as you see fit, filter them out, etc) and then when ObserverB subscribes it will get a replay of the items already collected in the List before continuing with the source observable output.

This simple test should demonstrate the outcome

public void testSequenceNext() {
    final SimpleListObservable obs = new SimpleListObservable();

    final Observer<Integer> ob2 = Mockito.mock(Observer.class);

    obs.process().subscribe(new Observer<Integer>() {
        @Override
        public void onCompleted() {
            ob1Complete = true;
        }

        @Override
        public void onError(Throwable e) {
            e.printStackTrace();
        }

        @Override
        public void onNext(Integer integer) {
            System.out.println("ob1: " + integer);
            if (integer == 20) {
                obs.process().subscribe(ob2);
            }
        }
    });

    ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class);

    Mockito.verify(ob2, Mockito.times(30)).onNext(captor.capture());

    for (Integer value : captor.getAllValues()) {
        System.out.println(value);
    }

}

What do you think of this, I've made part of my API of course as I'm on a phone :

public class StreamOfSomething {
    new StreamOfSomething() {
        // source of events like
        events = Observable.range(0, 1_000_000_000)
                           .doOnNext(set::add) // some operation there
                           .map(Event::change)
                           .publish()
                           .refCount();
    }

    public Observable<Event> observeChanges() {
        return events.startWith(
                 Observable.just(Event.snapshot(set))); // start stream with generated event
    }
}

And the client can do something like :

Observable.timer(2, 4, TimeUnit.SECONDS)
          .limit(2)
          .flatMap(t -> theSourceToWatch.observeChanges().limit(10))
          .subscribe(System.out::println);

Note however if you are in a multithreaded environment you may have to synchronize when you are subscribing to block any modification, otherwise the list may change before it get's emitted. Or rework this class completely around observables, I don't know yet how to achieve this though.

Sorry to post this 2 years later, but I had the same need and found this question unanswered.

What I did is the following:

public Observable<Event> observe() {
    return Observable.defer(() -> 
        subject.startWith(createInitialEvent())
    );
}

The idea is the following:

  • defer() executes the passed-in lambda expression when an observer subscribes to the Observable returned by the method observe(). So basically, it executes subject.startWith(...), which returns an Observable that is the actual source of event for the subscriber.
  • subject.startWith(...) emits an initial event (specified by startWith(...)) followed by those emitted by the subject.

So, if I come back to the original post: if an observer starts it subscription between event 1,2 then it should receive the following events: InitialEvent, 2, 3, 4, 5.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM