8
Android Studio 3.2 Canary 8
com.squareup:otto:1.3.8
io.reactivex:rxjava:1.3.7
kotlin 1.2.31

I am trying to send an event back to my Activity using the otto EventBus.

However, I am using RxJava to perform some background work and need the event to be sent after the first one completes. However, after post the event. The activity never receives it.

This event must do this on the main thread. The RxJava is on the IO thread. I am not sure what is the best way to do this:

Here is my code for the interactor that does the RxJava and EventBus post

class Interactors(private val eventBus: Bus) {
    fun transmitMessage(): Completable {
        return insertTransmission()
                .andThen(onTransmissionChanged()) /* Send event to the activity */
                .andThen(requestTransmission())
    }

    private fun insertTransmission(): Completable {
        return Completable.fromCallable {
            Thread.sleep(4000)
            System.out.println("insertTransmission doing some long operation")
        }
    }

    private fun requestTransmission(): Completable {
        return Completable.fromCallable {
            Thread.sleep(2000)
            System.out.println("requestTransmission doing some long operation")
        }
    }

    /* Need to send this event back to the activity/fragment */
    private fun onTransmissionChanged(): Completable {
        return Completable.fromCallable {
            System.out.println("onTransmissionChanged send event to activity")
            eventBus.post(TransmissionChanged())
        }
    }
}

Activity:

public class HomeActivity extends AppCompatActivity {
    private Bus eventBus = new Bus();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);

        eventBus.register(this);

        new Interactors(eventBus).transmitMessage()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe();
    }

    @Override
    protected void onDestroy() {
        eventBus.unregister(this);
        super.onDestroy();
    }

    @Subscribe
    public void onTransmissionChangedEvent(TransmissionChanged transmissionChanged) {
        System.out.println("onTransmissionChangedEvent");
    }
}

And the EventBus class:

class TransmissionChanged

This the output when I run the app:

insertTransmission doing some long operation
onTransmissionChanged

I am not sure if the eventBus.post(..) is blocking. Actually this should be done in the main thread as is posting back to the Activity to perform some update in the UI.

azizbekian
  • 60,783
  • 13
  • 169
  • 249
ant2009
  • 27,094
  • 154
  • 411
  • 609
  • 2
    Any particular reason why you are using RxJava1? – EpicPandaForce Apr 06 '18 at 18:37
  • 1
    It's a an old project I am working on. – ant2009 Apr 07 '18 at 01:26
  • Do the Completables print out properly? I don't see any logical errors; the code shown should work. Is this the actual code you are having problems with? Please check it contains everything and not more (i.e., you have `eventBus.register(this);` here but forgot it in your private code). – akarnokd Apr 07 '18 at 11:19
  • This is just a sample of a bigger project. However, the code is the same. If I run the `eventBus.post(...)` on its own. It works ok. However, if its called from within the `Completable.fromCallable(..)` it doesn't run. However, I need this to be called after the first callable and before the third one. Thanks. – ant2009 Apr 07 '18 at 15:14
  • A very noob answer but can you try posting the event from within this block `new Handler(Looper.getMainLooper()).post(() -> {});` – Jude Fernandes Apr 08 '18 at 17:54
  • 1
    After some searching, it seems you [can't use Otto to post back to the main thread](https://stackoverflow.com/a/27070780/61158). – akarnokd Apr 08 '18 at 19:09
  • Do you want both background requests to execute at the same time or in sequence? – iagreen Apr 09 '18 at 06:20
  • @iagreen they have to work in sequence. But as I am updating the UI the event has to be published in the main thread. – ant2009 Apr 09 '18 at 16:57

4 Answers4

2

Do you really need to mix an EventBus and RxJava? For me this introduces extra complexity without a lot of benefit to it. Your use-case seems like a perfect example to use an Rx stream, doing some work on each emission (in your case updating the UI via onTransmissionChangedEvent()).

I'd change transmitMessage() method to something like this:

fun transmitMessage(): Observable<TransmissionChanged> {
    return Observable.create(ObservableOnSubscribe<TransmissionChanged> { emitter ->
        insertTransmission()

        emitter.onNext(TransmissionChanged())  // perform this after the long running operation from insertTransmission() has finished

        requestTransmission()

        emitter.onComplete()   // after the long running operation from requestTransmission() has finished
    })
}

I guess you need some extra data to update your UI accordingly - this is encapsulated in TransmissionChanged class - include whatever you need there. One thing to be aware of - using Observable.create() is dangerous in RxJava 1. I don't remember what was the safe way of doing so and don't have a project with RxJava 1 to experiment with ... but there was a factory method in the Observable class that could do the job safely.

Using the above, your Activity code becomes cleaner as well. There's no need for Otto anymore, as all your operations are handled via the single Rx stream.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_home);

    new Interactors()
        .transmitMessage()
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(transmission -> onTransmissionChangedEvent(transmission),
                throwable -> handleError(throwable),
                () -> handleCompletion()
        );
}
Vesko
  • 3,750
  • 2
  • 23
  • 29
  • I think you are write that making the EventBus being incorporated in the RxJava is too complicated. What do you think about a subject that can publish and observe. onTransmissionChangeEvent could be the published event. `ObservableOnSubscribe` is that RxJava2. My current implementation uses RxJava1. Thanks. – ant2009 Apr 09 '18 at 17:19
  • Having a `Subject` you'll publish events to is just like implementing a mini `EventBus` using RxJava :) Still I think it'll be cleaner than mixing the two. Indeed `ObservableOnSubscribe` is part of RxJava 2 (which I used to write the example code). The idea is to find the **safe** way of creating a custom observable in RxJava 1 for your `transmitMessage()`. – Vesko Apr 09 '18 at 22:43
1

Not allowing the receiver to specify which thread it would like to receive events on is a short coming of Otto. It enforces that all calls need to be on the same thread (defaults to the main thread). It is up to the caller to get be on the correct thread. I much prefer EventBus by GreenRobot. You change which thread you want to receive on with an annotation. So, my first suggestion would be, if you are not too invested in Otto yet, is to consider using EventBus instead.

If you are not in a position to rework all your event bus code, you can post back to the main looper by allocating a Handler. It is quick and easy, but feels a little like stepping out of rx framework.

private fun onTransmissionChanged(): Completable {
    return Completable.fromCallable {
        System.out.println("onTransmissionChanged send event to activity")
        Handler(Looper.getMainLooper()).post {
            eventBus.post(TransmissionChanged())
        }
    }
}

If you are calling this a lot, you may want to cache the Handler and pass it into your Interactors constructor.

If you want to stick with RxJava schedulers, you can pass a Scheduler into your constructor to indicate where you want to do your background work instead of using subscribeOn. In transmitMessage, use it schedule the background ops while forcing the eventBus.post to the main thread as follows --

class Interactors(private val eventBus: Bus, private val scheduler: Scheduler) {
    fun transmitMessage(): Completable {
        return insertTransmission()
                .subscribeOn(scheduler)
                .observeOn(AndroidSchedulers.mainThread())
                .andThen(onTransmissionChanged()) /* Send event to the activity */
                .observeOn(scheduler)
                .andThen(requestTransmission())
    }
    // Rest of the class is unchanged

}

in this case, you will use it in HomeActivity as follows --

new Interactors(eventBus, Schedulers.io()).transmitMessage()
              .observeOn(AndroidSchedulers.mainThread())
              .subscribe();
iagreen
  • 31,470
  • 8
  • 76
  • 90
  • this doesn't work because concat operator only flows ahead when the upstream sources are completed check here http://reactivex.io/documentation/operators/concat.html, it doesn't have a scope for interleaving like merge – rahul.taicho Apr 09 '18 at 06:15
  • I may have misunderstood, but I thought the OP wanted the background ops to be sequential and not parallel, asked above for clarification. – iagreen Apr 09 '18 at 06:22
  • 1
    Sir, I posted my answer and now I noticed it is pretty much the same as yours. Removed mine, not sure why you got downvote. – azizbekian Apr 09 '18 at 15:51
  • 1
    Thanks, both your samples worked. But thinking about it I think adding eventBus to RxJava is making it more complicated. I am thinking maybe a Subject to publish and observe. – ant2009 Apr 09 '18 at 17:16
  • @ant2009 That is reasonable. As noted in another commit, it is `Subject` is akin to an rx eventbus. If you are looking at architecture design options, I recommend looking at this presentation by Jake Wharton. http://jakewharton.com/the-state-of-managing-state-with-rxjava/ for some ideas of how to think about state. – iagreen Apr 11 '18 at 00:12
  • @azizbekian Thank you. As for the downvote, you never know, part of the randomness that comes with SO sometimes. – iagreen Apr 11 '18 at 00:14
1

It is possible that your activity/fragment is not started/attached while posting the event so they haven't registered to the eventBus yet. By that the event was post, but there are no subscribers (or maybe there are other subscribers somewhere else). Maybe you should use Sticky Events to make that event "awake" so your activity/fragment will still be able to handle it.

For using EventBus events as RxJava code, I do something as follows:

public class EventBusRx {
    private static EventBusRx ourInstance = new EventBusRx();

    public static EventBusRx getInstance() {
        return ourInstance;
    }

    private EventBusRx() {}

    public final Subject<Integer> eventName = PublishSubject.create();`
}`

And then listening to such event:

EventBusRx.getInstance().eventName
            .subscribeOn(AndroidSchedulers.mainThread())
            .doOnNext(o -> someAction())
            .subscribe();

And for posting an event:

public void postSomeEvent(int eventValue) {
    EventBusRx.getInstance().eventName.onNext(eventValue);
}

Also read about RxJava's Replay, which might be helpful for you.

lior_13
  • 577
  • 7
  • 18
1

Your Activity/Fragment should have this updated code:

@Override
public void onResume() {
    super.onResume();
    if (!eventBus.isRegistered(this))
        eventBus.register(this);
}

@Override
public void onDestroy() {
    super.onDestroy();
    if (mEventBus.isRegistered(this))
        mEventBus.unregister(this);
}

@Subscribe(threadMode = ThreadMode.MAIN)
public void onTransmissionChangedEvent(TransmissionChanged transmissionChanged) {
    System.out.println("onTransmissionChangedEvent");
}

Now your code for Interactors should be like this

new Interactors(eventBus).transmitMessage()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer() {

                @Override
                public void onSubscribe(Disposable d) {

                }

                @Override
                public void onNext(Object o) {
onTransmissionChanged();

                }

                @Override
                public void onError(Throwable e) {

                }

                @Override
                public void onComplete() {

                }
            });

Use on next on next method to call onTransmissionChanged().

AMT
  • 136
  • 5