Otto event bus runtime subscription for events - android

Is it possible in Otto to subscribe to events without using #Subscribe annotation ?
In my use case I do not know to which event my object should subscribe to at compile time. I wish to do it at runtime based on certain rules.

I suppose you can use a workaround like this,
public class MainClass {
private EventObserver eventObserver;
public MainClass() {
if(...someCondition...) {
eventObserver = new FirstEventObserver();
} else {
eventObserver = new SecondEventObserver();
}
}
public onEvent(Event event) {
if (event instanceOf FirstEvent) {
... handle event ...
} else if (event instanceOf SecondEvent) {
... handle event ...
}
}
}
public abstract class EventObserver {
protected MainClass mainClass;
public void setMainClass(MainClass mainClass) {
this.mainClass = mainClass;
}
protected void notifyMainClass(Event event) {
if (mainClass != null) {
mainClass.onEvent(event);
}
}
}
public class FirstEventObserver extends EventObserver {
public FirstEventObserver() {
bus.subscribe(this);
}
#Subscribe
public void onEvent(FirstEvent event) {
notifyMainClass();
}
}
public class SecondEventObserver extends EventObserver {
public SecondEventObserver() {
bus.subscribe(this);
}
#Subscribe
public void onEvent(SecondEvent event) {
notifyMainClass();
}
}
public abstract class Event {
}
public abstract class FirstEvent extends Event {
}
public abstract class SecondEvent extends Event {
}
Another workaround, which is a much cleaner solution. You can generate the event at runtime with the type you want.
public class MainClass {
#Subscribe
public void onEvent(Event event) {
if (event.getType() == EventType.FIRST_EVENT) {
... handle event ...
} else if (event.getType() == EventType.SECOND_EVENT) {
... handle event ...
}
}
}
public class Event {
public enum EventType {
FIRST_EVENT,
SECOND_EVENT
}
private EventType eventType;
public Event(EventType eventType) {
this.eventType = eventType;
}
public EventType getType() {
return eventType;
}
}

I created a framework for subscribing to events in runtime with Otto. Instead of having different Model classes for different event types one can have different EventDelegate for different events. These event delegate will just receive and event and pass them on to subscriber classes.
A Typical EventDelegate will look like this
public abstract class OttoEventDelegate {
private OttoEventListener ottoEventListener;
public OttoEventDelegate(OttoEventListener ottoEventListener) {
this.ottoEventListener = ottoEventListener;
}
public void register() {
BaseApplication.getInstance().getBus().register(this);
}
public void unregister() {
BaseApplication.getInstance().getBus().unregister(this);
}
public OttoEventListener getOttoEventListener() {
return ottoEventListener;
}
public void setOttoEventListener(OttoEventListener ottoEventListener) {
this.ottoEventListener = ottoEventListener;
}
}
This Approach is explained in this article. Also if you want to have a look at implementation. Its on github here.

Related

MVVM with Room and LiveData

My ViewModel class looks like this:
public class ViewModelMainActivity extends AndroidViewModel {
private LocalRepository localRepository;
private LiveData<List<Task>> allJob;
private LiveData<List<Task>> allShopping;
private LiveData<List<Task>> allOther;
public ViewModelMainActivity(#NonNull Application application) {
super(application);
localRepository = new LocalRepository(application);
allJob = localRepository.getAllJob();
allShopping = localRepository.getAllShopping();
allOther = localRepository.getAllOther();
}
public void insert(Task task) {
localRepository.insert(task);
}
public void delete(Task task) {
localRepository.delete(task);
}
public LiveData<List<Task>> getAllJob() {
return allJob;
}
public LiveData<List<Task>> getAllShopping() {
return allShopping;
}
public LiveData<List<Task>> getAllOther() {
return allOther;
}
}
Then in MainActivity calls two methods:
private void getAllJob() {
viewModelMainActivity.getAllJob().observe(this, new Observer<List<Task>>() {
#Override
public void onChanged(List<Task> tasks) {
if(tasks.size() == 0) {
linearLayoutActivityMain.setVisibility(View.VISIBLE);
} else {
linearLayoutActivityMain.setVisibility(View.INVISIBLE);
}
taskAdapter.setAllJobTasks(tasks);
}
});
}
private void getAllShopping() {
viewModelMainActivity.getAllShopping().observe(this, new Observer<List<Task>>() {
#Override
public void onChanged(List<Task> tasks) {
Log.i("Size", "Shopping: " + String.valueOf(tasks.size()));
if(tasks.size() == 0) {
linearLayoutActivityMain.setVisibility(View.VISIBLE);
} else {
linearLayoutActivityMain.setVisibility(View.INVISIBLE);
}
taskAdapter.setCurrentTasks(tasks);
}
});
}
Why when I save a task:
viewModelMainActivity.insert(task);
e.g. to the job category, both onChanged methods are called, not just the onChanged method in getAllJob.
How could I separate it? That only the onChanged method would be called for values ​​that have changed. Should I create separate ViewModels objects? But what about saving the task then? I would have to call the insert method three times for each object?

What is Design Pattern where we can pass the string name to a method and that method chooses which further method to call

I recently got following example where we are passing the action name to the method as string and then the method decides the function that needs to be called.
is this a good way of solving problem or is there some better way as well
public static final String ACTION_CHARGING_REMINDER = "charging-reminder";
public static void executeTask(Context context, String action) {
if (ACTION_INCREMENT_WATER_COUNT.equals(action)) {
incrementWaterCount(context);
} else if (ACTION_DISMISS_NOTIFICATION.equals(action)) {
NotificationUtils.clearAllNotifications(context);
} else if(ACTION_CHARGING_REMINDER.equals(action)){
issueChargeReminder(context);
}
}
I'd do something like this. This can be extended as much as you want, and obviously just an example:
static abstract class ActionHandler {
private String action;
public ActionHandler(String action) {
this.action = action;
}
public boolean canHandleAction(String input) {
return this.action.equals(input);
}
public abstract void handleAction();
}
static class OneActionHandler extends ActionHandler {
public OneActionHandler(String action) {
super(action);
}
#Override
public void handleAction() {
//...
}
}
static class TwoActionHandler extends ActionHandler {
public TwoActionHandler(String action) {
super(action);
}
#Override
public void handleAction() {
//...
}
}
static class Test {
private ActionHandler[] handlers;
public Test() {
handlers = new ActionHandler[]{new OneActionHandler("action1"), new TwoActionHandler("action2")};
}
public void handleAction(String action) {
for(ActionHandler i : handlers) {
if(i.canHandleAction(action)) {
i.handleAction();
break;
}
}
}
}
This sounds a lot like the react/redux, action/reduction pattern.
Reducers specify how the application's state changes in response to
actions sent to the store. Remember that actions only describe what
happened, but don't describe how the application's state changes.

PublishSubject subscriber is not receiving events

I have a class ViewModel that exposes a PublishSubject binder.
ViewModel
public class ViewModel {
private PublishSubject<ActionsEvent> binder = PublishSubject.create();
private Service service = createService();
#Override
public Observable<ActionsEvent> getBinder() {
return binder.doOnSubscribe(initialize());
}
private Action0 initialize() {
return new Action0() {
#Override
public void call() {
service.getActions().subscribe(new Action1<Action>() {
#Override
public void call(Action action) {
Log.d(TAG, "So far, so good");
binder.onNext(new ActionFetchedEvent(action));
}
});
}
};
}
}
And in the Activity, it subscribe an action to be executed when each event is fetched.
Activity
public class MyActivity extends Activity {
#Override
public void onCreate(Bundle savedInstance) {
//More code
viewModel.getBinder().subscribe(new Action1<ActionsEvent>() {
#Override
public void call(ActionsEvent event) {
Log.d(TAG, "This is not printed!!");
paintActionInUserInterface(event.getAction());
}
});
}
}
Service
public interface ActionsService {
#GET("/actions")
Observable<Action> getActions(); //Performs an HTTP request with Retrofit
}
ActionFetchedEvent
public class ActionFetchedEvent implements ActionsEvent {
private Action action;
//getters and setters
}
But subscriber doesn't receive the event. Why?
it is because you do not create an Subject with .create() factory-method, and onSubscribe will be called before the callback of your subscription, so you will subscribe too late and miss the element. You could use a BahaviourSubject, which will replay the last element, if you subscribe.
Could you please tell us what you want to achieve, because I think you could compose the observables in a way better way, than subscribing and posting onNext onto the subject.
Please have a look at my example. I use RxJava2 as environment.
public class ViewModelTest {
class ActionsEvent {
}
class ActionFetchedEvent extends ActionsEvent {
public ActionFetchedEvent(ActionsEvent actionEvent) {
}
}
interface Service {
public Observable<ActionsEvent> getActions();
}
class MyViewModel {
private BehaviorSubject<ActionsEvent> binder;
private Service service;
public MyViewModel(Service service) {
this.service = service;
this.binder = BehaviorSubject.create();
}
public Observable<ActionsEvent> getBinder() {
return binder.doOnSubscribe(disposable -> {
service.getActions().subscribe(action -> {
binder.onNext(new ActionFetchedEvent(action));
}
);
});
}
}
#Test
public void name() throws Exception {
Service mock = mock(Service.class);
MyViewModel viewModel = new MyViewModel(mock);
when(mock.getActions()).thenAnswer(invocation -> {
return Observable.just(new ActionsEvent());
});
TestObserver<ActionsEvent> test = viewModel.getBinder().test();
test.assertValueCount(1);
}
}

Configuring RxJava to Send Data to activity from GCMListenerService

I am trying to send an update to my Activity from my GCMServiceListener so, I am using RxJava/RxAndroid And created a BusClass for handling sending and Observers
public class ClientBus {
//private final PublishSubject<Object> _bus = PublishSubject.create();
// If multiple threads are going to emit events to this
// then it must be made thread-safe like this instead
private final Subject<Object, Object> _bus = new SerializedSubject<>(PublishSubject.create());
public void send(Object o) {
_bus.onNext(o);
}
public Observable<Object> toObserverable() {
return _bus;
}
public boolean hasObservers() {
return _bus.hasObservers();
}
}
And in my Application Class I did this to initialize the BusClass
private ClientBus clientBus;
public ClientBus getRxBusSingleton() {
if (clientBus == null) {
clientBus = new ClientBus();
}
return clientBus;
}
In the activity I want to receive the message, I registered a CompositeSubscription and get a reference to my ClientBus class from the Application Class
clientBus = ((MyApplication) getApplicationContext()).getRxBusSingleton();
#Override
protected void onStart() {
super.onStart();
initSubscriptions();
}
#Override
protected void onStop() {
super.onStop();
_subscriptions.unsubscribe();
}
void initSubscriptions() {
_subscriptions = new CompositeSubscription();
_subscriptions.add(clientBus.toObserverable().subscribe(new Action1<Object>() {
#Override
public void call(Object event) {
Log.e("New Event", "Event Received");
if (event instanceof MyGcmListenerService.Message) {
String msg = ((MyGcmListenerService.Message) event).getMessage();
if (msg.equals("Update Available")) {
scheduleArrayList = getSchedules();
scheduleAdapter = new ScheduleAdapter(getApplicationContext(), scheduleArrayList, ScheduledUberActivity.this);
scheduledList.setAdapter(scheduleAdapter);
scheduleAdapter.notifyDataSetChanged();
} else if (msg.equals("Refresh")) {
fetchTrips();
}
}
}
}));
}
And from the MyGcmListenerService class I did this when I get a new notification
private void sendRefreshNotif() {
if (clientBus.hasObservers()) {<--It enters the if cause the Log prints. But, the activity doesn't get the message
Log.e("Obervers", "Observers aren't null");
clientBus.send(new Message("Refresh"));
}
}
What I don't understand is why isn't it working here? I use it to interact between activities and fragments. I closed my application to check if the notification comes in, It'll enter this block if (clientBus.hasObservers()) { but it didn't and starting the app and testing the Observer, it notices there's an active Observer. Any help? Thanks.
It seems like you used different instances of the ClientBus class in CompositeSubscription and MyApplication.
Try to make a singleton from ClientBus class, it works fine for me.
public class ClientBus {
public ClientBus(SingletonAccessor accessor) {}
private static ClientBus instance;
private static class SingletonAccessor{}
public static ClientBus getInstance() {
if (instance == null) instance = new ClientBus(new SingletonAccessor());
return instance;
}
private final Subject<Object, Object> mBus = new SerializedSubject<>(PublishSubject.create());
public void send(Object o) {
mBus.onNext(o);
}
public Observable<Object> toObserverable() {
return mBus;
}
public boolean hasObservers() {
return mBus.hasObservers();
}
}

Thread enforcement per method with Otto

I'm using Otto as my event bus in my android application.
I had to make sure that certain events are called in the main thread, or not in the main thread, for that I created my own bus class which uses Otto like so:
class MyEventBus {
private final Bus anyThreadBus;
private final Bus mainThreadBus;
private final Bus notMainThreadBus;
private final Handler mainThreadHandler;
public enum Strategy {
Any,
Main,
NotMain
}
MyEventBus() {
this.anyThreadBus = new Bus(ThreadEnforcer.ANY);
this.mainThreadBus = new Bus(ThreadEnforcer.MAIN);
this.notMainThreadBus = new Bus(ThreadEnforcer.ANY);
this.mainThreadHandler = new Handler(Looper.getMainLooper());
}
public void register(Object object) {
this.register(object, Strategy.Any);
}
public void register(Object object, Strategy strategy) {
switch (strategy) {
case Main:
this.mainThreadBus.register(object);
break;
case NotMain:
this.notMainThreadBus.register(object);
break;
case Any:
default:
this.anyThreadBus.register(object);
}
}
public void unregister(Object object) {
try {
this.anyThreadBus.unregister(object);
} catch (Exception e) {}
try {
this.mainThreadBus.unregister(object);
} catch (Exception e) {}
try {
this.notMainThreadBus.unregister(object);
} catch (Exception e) {}
}
public void post(Object event) {
this.anyThreadBus.post(event);
this.enforceOnMainThread(event);
this.enforceOnNotMainThread(event);
}
public void post(Object event, Strategy strategy) {
switch (strategy) {
case Main:
this.enforceOnMainThread(event);
break;
case NotMain:
this.enforceOnNotMainThread(event);
break;
case Any:
default:
this.anyThreadBus.post(event);
}
}
private void enforceOnNotMainThread(final Object event) {
if (MyEventBus.onMainThread()) {
// MyApplication.pool() returns a shared thread pool for the application
MyApplication.pool().execute(new Runnable() {
#Override
public void run() {
notMainThreadBus.post(event);
}
});
} else {
this.notMainThreadBus.post(event);
}
}
private void enforceOnMainThread(final Object event) {
if (MyEventBus.onMainThread()) {
this.mainThreadBus.post(event);
} else {
this.mainThreadHandler.post(new Runnable() {
#Override
public void run() {
mainThreadBus.post(event);
}
});
}
}
private static boolean onMainThread() {
return Looper.myLooper() == Looper.getMainLooper();
}
}
I have two questions:
In my post methods I post the event on all of the 3 buses, and that's because I don't know if the posted event has a registered class in a certain bus. Is there a way to know? something like:
if (this.anyThreadBus.has(event)) { ... }
Is there a way to do this other than maintaining a map of event classes to registered classes per bus?
Currently each registered class has an enforcement per call it made to register. But it would be best if I couldn't specify the enforcement per method and not for the entire class, something like:
#Subscribe(enforcement = Strategy.Main)
public void handleMyEvent(MyEvent event) { ... }
Can that be done somehow?
Thanks.
Maybe it's time to switch your EventBus implementation?
Check out if that one would make live easier for you:
http://greenrobot.org/eventbus/documentation/delivery-threads-threadmode/

Categories

Resources