Moving away from the Event Bus with RxJava and Dagger 2

Published Jul 30, 2017
Moving away from the Event Bus with RxJava and Dagger 2

A few weeks ago, I made a post about how to make an event bus with RxJava. While this solution works, it goes against the coding style encouraged in the reactive programming model. Instead of passing around events, I could expose the Observables so that I can publish or subscribe directly to the source. It was suggested to me that I try out Dagger, as it does exactly what I want.

I had never used Dagger prior to this, but it’s simple to get started. I made a module, which provides the implementation for my Observables. Then I created a component, which uses the module I made to get my Observables. Dagger will then create the implementation of my component, and with that I get access to my Observables.

How does it work?

I created a sample GitHub project to demonstrate my solution. You can see the Dagger module below.

@Module
public class BusModule {

    public static final String PROVIDER_TOP_SUBJECT = "PROVIDER_TOP_SUBJECT";
    public static final String PROVIDER_BOTTOM_SUBJECT = "PROVIDER_BOTTOM_SUBJECT";

    @Provides
    @Singleton
    @Named(PROVIDER_TOP_SUBJECT)
    static PublishSubject<String> provideTopSubject() {
        return PublishSubject.create();
    }

    @Provides
    @Singleton
    @Named(PROVIDER_BOTTOM_SUBJECT)
    static PublishSubject<String> provideBottomSubject() {
        return PublishSubject.create();
    }
}

Since I’m using the same class for each Observable, I need to use the @Named annotation to differentiate them. I’m also using the @Singleton annotation to ensure that I only ever have one instance of each Observable. If a different instance was returned each time, this would be useless. I need the publisher to communicate to the subscriber on the same Observable instance.

To use this Dagger module I created the following Dagger component.

@Component(modules = BusModule.class)
@Singleton
public interface BusComponent {

    @Named(BusModule.PROVIDER_TOP_SUBJECT)
    PublishSubject<String> getTopSubject();

    @Named(BusModule.PROVIDER_BOTTOM_SUBJECT)
    PublishSubject<String> getBottomSubject();
}

Similar to the module, I have to use the @Singleton annotation so that I only have one instance of my Observables. In fact, it won’t compile without this, because the module would be using a different scope than the component.

Applying @Singleton to my component confused me though. When I set the @Singleton annotation on the providers in my module, it made it so there’s only a single instance. But, that same annotation on my component does not have the same effect. I expected that if I called DaggerBusComponent.create() or DaggerBusComponent.builder().build() it would always return the same instance. I found out that this is completely wrong. If you want to share instances of your objects across your application, you either need to make your own singleton class to store it, or extend Application and store it there.

I chose to store it in my App class, seen below.

public class App extends Application {
    
    private static BusComponent sBusComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        sBusComponent = DaggerBusComponent.create();
    }

    public static BusComponent getBusComponent() {
        return sBusComponent;
    }

}

Now I can use this wherever I want, like so:

//Subscribe to a subject
App.getBusComponent().getTopSubject().subscribe((message) -> {
            if (mMessageView != null) {
                mMessageView.setText(message);
            }
        });

//Send an item to subscribers of a subject
App.getBusComponent().getBottomSubject().onNext("Hello!");

Lastly, to manage my subscriptions, I created a CompositeDisposable in my BaseFragment class.

public class BaseFragment extends Fragment {

    private CompositeDisposable mSubscriptions;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mSubscriptions = new CompositeDisposable();
    }

    protected CompositeDisposable getCompositeDisposable() {
        return mSubscriptions;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mSubscriptions.dispose();
    }
}

//Example useage:
Disposable subscription = App.getBusComponent().getBottomSubject().subscribe((message) -> {
  if (mMessageView != null) {
    mMessageView.setText(message);
  }
});

getCompositeDisposable().add(subscription);

Below is the final result: You can see the top fragment is subscribed to the bottom, and the bottom to the top. When a button is tapped from one side, it sends a message to the other.
example.gif

Wrap up

Now that I’m exposing the Observables, I don’t have to cast objects or check their types anymore. Memory leaks are still a concern though, and having to keep track of each subscription feels a bit clunky. I considered making my own Observable that you can subscribe to, attached to a lifecycle (like a Fragment or Activity). The issue is that then my subscribers need to be listening to the lifecycle events. So my Fragment would need to have its own LifecycleObservable that each subscriber is watching, so they know when to pause, resume, and unsubscribe (in onDestroy).

If anyone has an elegant solution to Subscriptions being attached to the lifecycle I would love to see it. There is a library called RxLifecycle which is close to what I am looking for, but not quite. It allows you to bind your Observables to the lifecycle, but I actually want my Subscriptions bound to the lifecycle. My Observables are bound to the lifecycle of the application, but their Subscriptions need to be bound to Activities and Fragments.


This post was originally published by the author here. This version has been edited for clarity and may appear different from the original post.

Discover and read more posts from Pierce Zaifman
get started
Enjoy this post?

Leave a like and comment for Pierce

1