Narayan Acharya
Narayan Acharya

Reputation: 1499

Communicating between components in Android

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;
            }
        }
}

Upvotes: 2

Views: 885

Answers (2)

Budius
Budius

Reputation: 39836

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;
  }

}

Upvotes: 1

LaurentY
LaurentY

Reputation: 7653

To communicate between components (Activity, Fragment) you have to use an event bus. In android, you could choose between:

A blog to explain this.

Upvotes: 0

Related Questions