Search code examples
androideventsotto

Is it the proper way to use an event bus?


I am planning to add Otto event bus to decouple my communications. One of the things I want use event bus for is to communicate between a button click handler and the activity.

The idea is that the button click (from my custom view) would generate a text submission event which would notify the activity. The activity would then decide what to do with it. If the activity deems it proper, it would send the text to a service for upload or whatever.

Is this a proper way to use an event bus?

Also, what are some good practices when using event buses?


Solution

  • I still think this question should be closed as not proper for the StackOverflow model.

    But for anyone looking on how on can organize user events around a Bus, that's kinda of how we've done on the place I work.

    Remember, that type of structure only makes sense if you're creating a big project where achieving a high level of separation makes the life of a team of developers easier. For small, quick projects or test apps that's too much effort.

    PS.: all the code below is typed 100% by heart without checking any real code, so there will be typos and small errors, but should be enough to get an idea of the approach. I also didn't write any annotation like @override, too lazy for it.

    First: Activity overrides getSystemService to supply a Bus via Context and register/unregister event handlers as needed.

    public MyActivity extends AppCompatActivity {
        private static final String BUS_SERVICE = "bus_service";
    
        private List<EventHandler> eventHandlers = new ArrayList();
        private Bus bus = new Bus();
    
        public void onCreate(Bundle savedState){
            super.onCreate(savedState);
                .... layout creation, etc, etc, etc
    
                if(isLoggedIn()) {
                    eventHandlers.add(new LoggedUserNavigationHandler());
                    eventHandlers.add(new RestPostRequestHandler());
                } else{
                    eventHandlers.add(new GuestUserNavigation());
                }
                eventHandlers.add(new AnalyticsTrackingHandler());
    
                if(DEBUG) {
                    // log all events in debug mode
                    eventHandlers.add(new EventHandler(){
                                 @Subscribe
                                 public void onEvent(Object o){
                                     Log.d(TAG, "Event: " + o.toString);
                                 }
                            });
                }
    
            }   
        }
    
        public Object getSystemService(String name){
            if(BUS_SERVICE.equals(name)) return bus;
            else return super.getSystemService(name);
        }
    
        public void onStart(){
            super.onStart();
            for(int i=0, size=eventHandlers.size(); i<size; i++) {
                 eventHandlers.get(i).activity = this; // pass reference, might be usefull
                 bus.register(eventHandlers.get(i));
            }
        }
        public void onStop(){
            for(int i=0, size=eventHandlers.size(); i<size; i++) {
                 bus.unregister(eventHandlers.get(i));
                 eventHandlers.get(i).activity = null;
            }
            super.onStop();
        }
    }
    

    Then: You have all the RecyclerView.ViewHolder (or custom widget) to be the click listener and dispatch appropriate events. For example in a ViewHolder for a photo item.

    public class PhotoHolder extends ViewHolder implements OnClickListener {
    
         TextView user;
         ImageButton like;
         ImageView photo;
    
         Photo data; // assume this was set during `bindViewHolder`
    
         public PhotoHolder(View itemView) {
              super(itemView);
              user = (TextView) itemView.findViewById(...
              like = (ImageButton) itemView.findViewById(...
              photo = (ImageView) itemView.findViewById(...
    
              user.setOnClickListener(this);
              like.setOnClickListener(this);
              photo.setOnClickListener(this);
         }
    
         public void onClick(View view){
              switch(view.getId()){
                  case R.id.user:
                       ((Bus)view.getSystemService(BUS_SERVICE))
                                  .post(new Event.PhotoEvent.UserTap(data);
                     break;
                  case R.id.like:
                       ((Bus)view.getSystemService(BUS_SERVICE))
                                  .post(new Event.PhotoEvent.LikeUnlike(data);
                     break;
                  case R.id.photo:
                       ((Bus)view.getSystemService(BUS_SERVICE))
                                  .post(new Event.PhotoEvent.PhotoTap(data);
                     break;
              }
         }
    }
    

    and the last of course: is to create those events objects and add all the events to your appropriate handlers.

    // add all the app events under this class, or maybe create a `Event` package and then all the events in that package
    public final class Event {
    
        public static class PhotoEvent {
              public final Photo data;
              public Photo(Photo data){
                  this.data=data;
              }
    
              public static class UserTap extends PhotoEvent{
                    // copy matching constructor
              }
    
              public static class LikeUnlike extends PhotoEvent{
                    // copy matching constructor
              }
    
              public static class PhotoTap extends PhotoEvent{
                    // copy matching constructor
              }
    
        }
    }
    

    finally, handling events

    public class RestPostRequestHandler {
    
         @Subscribe
         public void onPhotoLikeUnlike(Event.Photo.LikeUnlike event){
             // make your POST request here
         }
    }
    

    a handler for navigating:

    public class LoggedUserNavigationHandler extends EventHandler{
    
         @Subscribe
         public void on(Event.Photo.UserTap event){
              Intent i = new Intent( ... create here intent for the "user profile"
              // activity reference was passed during onStart
              activity.startActivity(i);
         }
    }
    

    a handler for analitics:

     public class AnalyticsTrack {
         @Subscribe
         public void on(Event.Photo.UserTap event){
              // send event "user tap" ?
         }
     }
    

    I agree with some of the comments that it's possible to create a huge, weird spaghetti code when having "tap" events going through the bus. But if from the start a good structured approach is defined and all the developers follow it, you can achieve a project that is easy to follow and with a very clear separation of responsibilities.