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?
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.
Related
I have a simple use case where:
Activity1 create a fragment1
fragment1 after creation notify to activity that it is created and update its activity1 views.
activity1 after getting notification update fragment1 views.
I am using rxandroid , sublibrary rxlifecycle components and android , but i am still in learning phase , there was not even rx-lifecycle tag on stackoverflow , so i am still struggling to understand the flow of this library..
Edit
I prefer not to use EventBus , it's just like everyone shouting at everyone to do something, so Rxjava Observable approach will be much useful
For posting information from fragment to activity, you should use an event bus for informing activity about fragment creation (replacement to the callbacks and the mess they created).
Sample code for event bus with RxJava is:
public class SampleEventsBus {
private static final String TAG = SampleEventsBus.class.getSimpleName();
private static final String TAG2 = SampleEventsBus.class.getCanonicalName();
public static final int ACTION_FRAGMENT_CREATED = 1;
public static final int ACTION_FRAGMENT_OTHER = 2;
private static SampleEventsBus mInstance;
public static SampleEventsBus getInstance() {
if (mInstance == null) {
mInstance = new SampleEventsBus();
}
return mInstance;
}
private SampleEventBus() {}
private PublishSubject<Integer> fragmentEventSubject = PublishSubject.create();
public Observable<Integer> getFragmentEventObservable() {
return fragmentEventSubject;
}
public void postFragmentAction(Integer actionId) {
fragmentEventSubject.onNext(actionId);
}
}
Then from your fragment you can call:
SampleEventsBus.getInstance().postFragmentAction(SampleEventsBus.ACTION_FRAGMENT_CREATED);
from onAttach() or onViewCreated() or any place you prefer.
Also, in activity you will need to put the following code to listet to your event bus:
SampleEventsBus .getInstance().getFragmentEventObservable().subscribe(new Subscriber<Integer>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Integer actionId) {
if(actionId == SampleEventsBus.ACTION_FRAGMENT_CREATED) {
//do any required action
}
}
});
For the second part, i.e. to update the fragment from activity, I won't recommend using this method as it will lead to unnecessary complexity, Instead use the "original way" as:
Create a method in Fragment as updateView(Object obj)
In onNext(), get the desired fragment as SampleFragment fragment = (SampleFragment)getSupportFragmentManager().findFragmentByTag("TAG");
call fragment.updateView(obj);
Hope this helps.
Two points to consider:
Just because you use an EventBus does not mean that it needs to be
global. You can have multiple event buses if you want, and you can just
share a single one between two components (Activity and Fragment).
There are several examples in the RxJava documentation that show
how to implement event bus functionality using RxJava
By Using an event bus, you can simplify things greatly, by disassociating the whole process from the Android lifecycle.
So I have an Activity. The Activity hosts a ViewPager with tabs, each tab holding a Fragment in it. The Fragments themselves have a RecyclerView each. I need to communicate changes from the RecyclerView's adapter to the activity.
Currently, I am using the listener pattern and communicating using interface between each of the components. i.e I have an interface between the RecyclerView's adapter and the Fragment holding it. Then an interface from the Fragment to the ViewPager's FragmentStatePagerAdapter which is creating all the Fragments. And 1 more interface between the ViewPager's adapter and the Activity hosting the ViewPager. I feel that there are too many interfaces for all the components because of how they are structured.
Currently I am not facing issues as such but I think the listener pattern is acting like an anti-pattern due to all the nested components. Instead of creating independent components I think the hierarchy will make it difficult for making code changes in future.
Am I doing it correctly or is there a better way to do it? Is this a case where I should use an Event Bus or Observer Pattern (If yes can you point me to some examples where someone overcame a similar problems using it)?
NOTE : If it matters, I need it to maintain a global object in the activity, something like a shopping cart where I can add or remove items and these items are present in RecyclerView's adapter from where I can add it to the cart and also increment or decrement the count for a particular item. The ViewPager and Tabs help segregate these items in various categories.
Edit 1 : Some code trying out #LucaNicoletti's approach -
I have skipped one level that is the level with the ViewPager's FragmentStatePagerAdapter. I guess that should not matter and stripped of some other code to keep it small.
MainActivity:
public class MainActivity extends AppCompatActivity implements View.OnClickListener, FoodAdapter.OnFoodItemCountChangeListener {
#Override
public void onFoodItemDecreased(FoodItemModel foodItemModel, int count) {
Log.d("Test", "Dec");
}
#Override
public void onFoodItemIncreased(FoodItemModel foodItemModel, int count) {
Log.d("Test", "Inc");
}
// Other methods here
}
Fragment hosting the Adapter:
public class FoodCategoryListFragment extends Fragment implements FoodAdapter.OnFoodItemCountChangeListener {
// Other boring variables like recyclerview and layout managers
FoodAdapter foodAdapter;
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Other boring intializations for recyclerview and stuff
// I set the click listener here directly on the adapter instance
// I don't have this adapter instance in my activity
foodAdapter.setOnFoodItemClickListener(this);
rvFoodList.setAdapter(foodAdapter);
}
}
The adapter class at the lowest level:
public class FoodAdapter extends RecyclerView.Adapter<FoodAdapter.FoodViewHolder> {
private OnFoodItemCountChangeListener onFoodItemCountChangeListener;
private List<FoodItemModel> foodItems;
// The interface
public interface OnFoodItemCountChangeListener {
void onFoodItemIncreased(FoodItemModel foodItemModel, int count);
void onFoodItemDecreased(FoodItemModel foodItemModel, int count);
}
// This is called from the fragment since I don't have the adapter instance
// in my activty
public void setOnFoodItemClickListener(OnFoodItemCountChangeListener onFoodItemCountChangeListener) {
this.onFoodItemCountChangeListener = onFoodItemCountChangeListener;
}
// Other boring adapter stuff here
#Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.bMinus:
onFoodItemCountChangeListener.onFoodItemDecreased(foodItems.get(getAdapterPosition()),
Integer.parseInt(etCounter.getText().toString()));
}
break;
case R.id.bPlus:
onFoodItemCountChangeListener.onFoodItemIncreased(foodItems.get(getAdapterPosition()),
Integer.parseInt(etCounter.getText().toString()));
}
break;
}
}
}
my comments were:
what you should/could do it's to have a global data repo which holds the shopping cart and listeners associated with changes to it. Like a singleton, like ShoppingCart.getInstance().addListener(this); and ShoppingCart.getInstance().addItem(new Item(id));
and
Yes. That's what I'm suggesting. Do not forget that this Singleton can never ever holds Context or Activity because u don't want to leak memory, so always call removeListener. On my opinion it would reduce dependency as all your view controllers only interact with the data model
and I'll add some code to exemplify as a proper answer.
Below is a very crude, typed by heart code, but it should give an idea. All the UI elements are only tied to the data, and not to each other.
Similar stuff could be implemented with libraries that provide observable pattern out of the box for data-only objects.
public class ShoppingCart {
private ShoppingCart single;
private static void init(){
.. init single if not null
}
private List<Item> items = new ArrayList<>();
public int numberOfItems;
public long totalPrice;
private static void addItem(Item item){
init()
single.items.add(item);
single.numberOfItems++;
single.totalPrice+=item.price;
dispatchChange();
}
private static void removeItem(Item item){
init();
single.numberOfItems--;
single.totalPrice-=item.price;
dispatchChange();
single.items.remove(item);
}
private void dispatchChange(){
// TODO: write real loop here
for(single.listeners) listener.onCartChanged(single.cart);
}
public interface Listener {
void onCartChanged(ShoppingCart cart);
}
private List<Listener> listeners = new ArrayList<>();
// TODO: addListener and removeListener code
public static class Item {
String id;
String name;
long price;
}
}
To communicate between components (Activity, Fragment) you have to use an event bus.
In android, you could choose between:
RxJava
Otto
Green Robot EventBus
A blog to explain this.
I'm start learning RxJava and I like it so far. I have a fragment that communicate with an activity on button click (to replace the current fragment with a new fragment). Google recommends interface for fragments to communicate up to the activity but it's too verbose, I tried to use broadcast receiver which works generally but it had drawbacks.
Since I'm learning RxJava I wonder if it's a good option to communicate from fragments to activities (or fragment to fragment)?. If so, whats the best way to use RxJava for this type of communication?. Do I need to make event bus like this one and if that's the case should I make a single instance of the bus and use it globally (with subjects)?
Yes and it's pretty amazing after you learn how to do it. Consider the following singleton class:
public class UsernameModel {
private static UsernameModel instance;
private PublishSubject<String> subject = PublishSubject.create();
public static UsernameModel instanceOf() {
if (instance == null) {
instance = new UsernameModel();
}
return instance;
}
/**
* Pass a String down to event listeners.
*/
public void setString(String string) {
subject.onNext(string);
}
/**
* Subscribe to this Observable. On event, do something e.g. replace a fragment
*/
public Observable<String> getStringObservable() {
return subject;
}
}
In your Activity be ready to receive events (e.g. have it in the onCreate):
UsernameModel usernameModel = UsernameModel.instanceOf();
//be sure to unsubscribe somewhere when activity is "dying" e.g. onDestroy
subscription = usernameModel.getStringObservable()
.subscribe(s -> {
// Do on new string event e.g. replace fragment here
}, throwable -> {
// Normally no error will happen here based on this example.
});
In you Fragment pass down the event when it occurs:
UsernameModel.instanceOf().setString("Nick");
Your activity then will do something.
Tip 1: Change the String with any object type you like.
Tip 2: It works also great if you have Dependency injection.
Update:
I wrote a more lengthy article
Currently I think my preferred approach to this question is this to:
1.) Instead of one global bus that handles everything throughout the app (and consequently gets quite unwieldy) use "local" buses for clearly defined purposes and only plug them in where you need them.
For example you might have:
One bus for sending data between your Activitys and your ApiService.
One bus for communicating between several Fragments in an Activity.
One bus that sends the currently selected app theme color to all Activitys so that they can tint all icons accordingly.
2.) Use Dagger (or maybe AndroidAnnotations if you prefer that) to make the wiring-everything-together a bit less painful (and to also avoid lots of static instances). This also makes it easier to, e. g. have a single component that deals only with storing and reading the login status in the SharedPreferences - this component could then also be wired directly to your ApiService to provide the session token for all requests.
3.) Feel free to use Subjects internally but "cast" them to Observable before handing them out to the public by calling return subject.asObservable(). This prevents other classes from pushing values into the Subject where they shouldn't be allowed to.
Define events
public class Trigger {
public Trigger() {
}
public static class Increment {
}
public static class Decrement {
}
public static class Reset {
}
}
Event controller
public class RxTrigger {
private PublishSubject<Object> mRxTrigger = PublishSubject.create();
public RxTrigger() {
// required
}
public void send(Object o) {
mRxTrigger.onNext(o);
}
public Observable<Object> toObservable() {
return mRxTrigger;
}
// check for available events
public boolean hasObservers() {
return mRxTrigger.hasObservers();
}
}
Application.class
public class App extends Application {
private RxTrigger rxTrigger;
public App getApp() {
return (App) getApplicationContext();
}
#Override
public void onCreate() {
super.onCreate();
rxTrigger = new RxTrigger();
}
public RxTrigger reactiveTrigger() {
return rxTrigger;
}
}
Register event listener wherever required
MyApplication mApp = (App) getApplicationContext();
mApp
.reactiveTrigger() // singleton object of trigger
.toObservable()
.subscribeOn(Schedulers.io()) // push to io thread
.observeOn(AndroidSchedulers.mainThread()) // listen calls on main thread
.subscribe(object -> { //receive events here
if (object instanceof Trigger.Increment) {
fabCounter.setText(String.valueOf(Integer.parseInt(fabCounter.getText().toString()) + 1));
} else if (object instanceof Trigger.Decrement) {
if (Integer.parseInt(fabCounter.getText().toString()) != 0)
fabCounter.setText(String.valueOf(Integer.parseInt(fabCounter.getText().toString()) - 1));
} else if (object instanceof Trigger.Reset) {
fabCounter.setText("0");
}
});
Send/Fire event
MyApplication mApp = (App) getApplicationContext();
//increment
mApp
.reactiveTrigger()
.send(new Trigger.Increment());
//decrement
mApp
.reactiveTrigger()
.send(new Trigger.Decrement());
Full implementation for above library with example -> RxTrigger
I am using a TextView inside of a Fragment.
I wish to update this TextView outside of the fragment (but also outside of an activity) from a callback class.
For example the user scrolls, the callback is called somewhere in my package, and I want the fragment view to be updated.
Can anybody explain how to do this? I did use a Local Broadcast Receiver but it wasn't fast enough in its updating.
Eventually looked at Otto but as we had Guava I implemented a singleton eventbus and used Guava publish/subscribe model to pass stuff around.
Otto however looks very similar.
Use Otto: http://square.github.io/otto/
public class UpdateEvent {
private String string;
public UpdateListEvent(String string) {
this.string = string;
}
public String getString() {
return string;
}
}
...
...
public void update() {
SingletonBus.INSTANCE.getBus().post(new UpdateListEvent(editText.getText().toString()));
}
...
public class FragmentA extends Fragment {
#Override
public void onResume() {
super.onResume();
SingletonBus.INSTANCE.getBus().register(this);
}
#Override
public void onPause() {
SingletonBus.INSTANCE.getBus().unregister(this);
super.onPause();
}
#Subscribe
public void onUpdateEvent(UpdateEvent e) {
//do something
}
}
public enum SingletonBus {
INSTANCE;
private Bus bus;
private SingletonBus() {
this.bus = new Bus(ThreadEnforcer.ANY);
}
public Bus getBus() {
return bus;
}
}
EventBus is a nice and elegant way for communication between modules in Android apps. In this way you should register your fragment as a event subscriber, and post a this specific event from other part of your code. Keep in mind that only UI thread can work with Views.
I don't exactly understand what you want to achieve and why BroadcastReceiver does not work for you, but you may either:
1) try using callbacks (if it is possible in your app design);
2) try using this or that event bus implementation;
Both would work pretty fast without much overhead, compared to broadcasting.
In case 2 you won't have to maintain callback dependencies/references.
Is it possible to make a secondary class to hold the OnClick Listener? Meaning not being created in the Activity class?
I just find that putting OnClick listeners in the main activity class is just messy and I would rather have them in separate classes. Thanks
Sure, that's possible. Just create a class that implements View.OnClickListener and set that as listener to the View. For example:
public class ExternalOnClickListener implements View.OnClickListener {
public ExternalOnClickListener(...) {
// keep references for your onClick logic
}
#Override public void onClick(View v) {
// TODO: add code here
}
}
And then set an instance of above class as listener:
view.setOnClickListener(new ExternalOnClickListener(...));
The parameterized constructor is optional, but it's very likely you'll need to pass something through to actually make your onClick(...) logic work on.
Implementing a class anonymously is generally easier to work with though. Just a thought.
Instead of putting the onCLicklistener in a separate class, why dont you try to define onClickListener outside onCreate()??
For e.g: like this
onCreate()
yourViewName.setOnClicklistener(listener):
Outside onCreate()
private OnClickListener listener = new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
};
Yes you can. However, making the listener an inner class has one advantage - it can access the fields and variables of your activity class directly. If you make it a separate class, and your listener actually need to access 5 views, your listener constructor might look like this:
MyListener listener = new MyListener(context, button, textView1, textView2, ratingBar, imageView);
Which is kinda bulky too. If your listener is simple, go ahead and make it a separate class. Otherwise, its up to you for readability.
Let me share how I code it using MVP. It's the best way to make clean code. Remember each class must have an interface to control it. I will show you the simplest one.
Suppose you want to Toast a text onClick and control it from another class. Here's how it works. Creating interfaces is for nothing but to connect with each other and you can review the code easily.
Create an interface for that MainActivity class.
public interface MainActivityView {
void showToast();
}
Create another interface for the Presenter class.
public interface IMainPresenter<V extends MainActivityView> {
/*Generic Type is to make sure it comes from MainActivity class only and to avoid other class to access it.*/
void onAttach(V mainView);
void onButtonClick();
}
Remember interfaces are nothing but to override method for each class.
Create a Presenter class
public class MainPresenter<V extends MainActivityView> implements IMainPresenter<V> {
private V mainActivityView;
#Override
public void onAttach(V mainActivityView) {
this.mainActivityView=mainActivityView;
}
public V getView() {
return mainActivityView;
}
#Override
public void onButtonClick() {
getView().showToast(); //This is the method from MainActivity controlling with this class
}
}
I'll skip, activity_main.xml layout because there's just a button with id="#+id/buttonId." In MainActivityClass,
public class MainActivity extends AppCompactActivity implements MainActivityView {
Button btn;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MainPresenter mainPresenter = new MainPresenter();
mainPresenter.onAttach(this);
btn = findViewById(R.id.buttonId);
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mainPresenter.onButtonClick(); //Here, check No.3 again!
}
});
}
#Override
public void showToast() {
Toast.makeText(this, "Hello", Toast.LENGTH_SHORT).show();
}
}
All I want to tell you is that. If you create objects in a class, it cannot make unit testing. That's why you're not seeing any new objects calling in android. So, you can use a singleton pattern (Here is Lazy Type) in Presenter class. I'll remove its interface and Generic to see it clearly.
public class MainPresenter {
private static final MainPresenter mainPresenter = new MainPresenter();
MainPresenter() {}
public static MainPresenter getInstance() {
return mainPresenter;
}
//Some methods here can be get it once you create an object with getInstance();
}
And so you can get its methods from MainActivity like this.
Instead of creating objects like this...
MainPresenter mainPresenter = new MainPresenter();
You can get it like this...
MainPresenter mainPresenter = mainPresenter.getInstance();
More example for singleton pattern can be found here,
https://www.journaldev.com/1377/java-singleton-design-pattern-best-practices-examples
Finally, using static is not a very good choice because it uses memory space whether you use it or not. And so, you can create objects within Application Layer get it with a Typecasting. I'm sure you don't need to unit test that Application layer.
public class AppLayer extends Application {
private MainPresenter mainPresenter;
#Override
public void onCreate() {
super.onCreate();
mainPresenter = new MainPresenter();
}
public MainPresenter getMainPresenter() {
return mainPresenter;
}
And you need to give a class name within Application in manifest.xml
<application
android:name=".AppLayer"
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
</application>
And you can get it with a Typecast in MainActivity like this!
MainPresenter mainPresenter = ((AppLayer)getApplication()).getMainPresenter();
For further studies, I suggest you learn ButterKnife, Dagger 2 and SOLID Principles. It will help you to create clean coding. Have fun!
You can do it. But just think that you will not have a reference to the activity, neither to it's attributes, including all the views. (unless you make them public or accessible with getters methods).
Also, be extra carefull with storing references to the activity or any members on the listener, since they might avoid the garbage collector from getting the listener memory back.
public class CommonClick {
public static void commonClick(final AppCompatActivity context){
context.findViewById(R.id.appbar).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
}
});
}
}