Dagger multibinding — Sets and Maps

Hamid Gharehdaghi
4 min readJun 7, 2016

--

it’s been awhile since Dagger as a Dependency Injection library is a popular and usual tool in developers toolbox. Getting familiar with all the features and use cases of this tool can help us writing cleaner codes. Multibinding is the feature which I’ve found it nice to match with some design patterns to solve problems and have better architecture.

In case you don’t use Dagger in your projects, I think it’s the time right now to start using it as soon as possible. Here you can find a good start point. Here I assume that you already know how to use Dagger for normal injections.

Set multibinding

Here as an example I’d like to implement(partially) a simple translator app which translates the texts. The app supports having different translators. To focus on the topic, I’ll try to skip the details as much as possible.

I start with writing a translator interface:

public interface Translator {
String getName();
String translate(String text);
}

An adapter for displaying the translators in spinner:

public class TranslatorAdapter extends BaseAdapter {
private List<Translator> translators = new ArrayList<>();

public TranslatorAdapter(Collection<Translator> translators) {
this.translators.addAll(translators);
}

@Override
public Object getItem(int position) {
return translators.get(position);
}

@Override
public View getView(int position, ...) {
Translator translator = translators.get(position);
//TODO inflate view and return
}
...
}

Usage on activity, fragment, … :

public class TranslatorActivity extends AppCompatActivity {

@Inject Set<Translator> translators;
...
private void initTranslatorsSpinner() {
TranslatorAdapter adapter = new TranslatorAdapter(
translators);
translatorsSpinner.setAdapter(adapter);
}

Note here that we don’t inject a single translator but a set of translators.

Google translator:

public class GoogleTranslator implements Translator {
private static final String NAME = "Google Translator";

@Override
public String getName() {
return NAME;
}

@Override
public String translate(String text) {
//TODO implement translation by Google services
}
}

And finally the @Module class:

@Module
public class TranslatorsModule {

@Provides(type = Provides.Type.SET)
@Singleton
public Translator provideGoogleTranslator(){
return new GoogleTranslator();
}
}

The only difference here (compare to a normal provider) is changing the provide type to Provides.Type.SET, which make us able to inject a set of translators.

With this architecture, adding new translators are so simple and with minimum possible changes( in order to respect to the OCP ). We have to only implement translator interface for the new translator and provide an instance of the new translator. Let’s implement a new translator:

public class LongmanTranslator implements Translator {
private static final String NAME = "Longman Translator";

@Override
public String getName() {
return NAME;
}

@Override
public String translate(String text) {
//TODO implement translation by Longman dictionary
}
}

And add a new provider to @Module:

@Module
public class TranslatorsModule {
...
@Provides(type = Provides.Type.SET)
@Singleton
public Translator provideLongmanTranslator(){
return new LongmanTranslator();
}
}

That’s it. You don’t have to change the other(UI & UX) parts. This is the nice decoupling feature which Dagger presents us with.

Map multibinding

Here let’s assume we have an app which sells items, and in the checkout process user can select payment method to do the payment. Each payment system has its own process and calculations. But each seller supports different payment systems, for example one seller can support only credit-card, and the other can support PayPal, and Cash-On-Delivery as well. Like previous example I’ll try to skip the details, and focus and concept more.

Let’s assume that when we get one seller information by API call, we get the supporting payment systems in a json format. By having this list of payments, we can display the payment methods list on a spinner, and user will select one of them to do the payment:

{
//Seller information here
...
"payment_methods": [
"credit-card",
"paypal",
...
]
}

For handling the payments, let’s create an interface for all handlers:

public interface PaymentHandler {
PaymentResult handlePayment(ShoppingCart cart);
}

In checkout process we will have something like this:

public class CheckoutProcess {

@Inject Map<String, PaymentHandler> paymentHandlers;
...
private void onCheckout() {
...
doPayment(selectedPaymentMethod);
}

private void doPayment(String selectedPaymentMethod) {
PaymentHandler paymentHandler =
paymentHandlers.get(selectedPaymentMethod);
paymentHandler.handlePayment(shoppingCart);
}
}

Note that we use a Map for mapping the payment methods names we get from seller information API to PaymentHandler. Let’s implement credit card payment handler:

public CreditCardPaymentHandler implements PaymentHandler {    @Override
PaymentResult handlePayment(ShoppingCart cart){
//TODO here is all the bussiness logic for handling the
//credit card payment method
}
}

Provider in @Module class:

@Module
public class PaymentModule {

@Provides(type = Provides.Type.MAP)
@StringKey("credit-card")

public PaymentHandler provideCreditCardPaymentHandler(){
return new CreditCardPaymentHandler();
}
}

We should use Provides.Type.MAP as provider type, and because Dagger wants to know what is the key for injecting into a Map in compile time, we should use @StringKey annotation to specify that.

if the map we’re going to inject to has different type of key we can use @IntKey, @LongKey, @ClassKey. For more specific map key types(e.g. enum, …) we can use @MapKey and create a new annotation for that. I’m not going to go into more details for this now.

With this architecture, if in the future we decide to add a new payment system we only have to add a new handler and provide that for injection. Let’s add a paypal payment handler:

public PaypalPaymentHandler implements PaymentHandler {@Override
PaymentResult handlePayment(ShoppingCart cart){
//TODO here is all the bussiness logic for handling the
//PayPal payment method.
}
}

And provide it in @Module class:

@Module
public class PaymentModule {
...
@Provides(type = Provides.Type.MAP)
@StringKey("paypal")

public PaymentHandler providePayPalPaymentHandler(){
return new PaypalPaymentHandler();
}
}

Conclusion

I tried to just explain the concepts and skip the implementation details by simplifying the examples as much as possible. For finding more details please take a look at the dagger documentation.

--

--