Part 8: Simple Ways To Stab With Dagger 2 ( Custom Scope)

Santosh Dhakal
6 min readJun 7, 2017

--

This is continuation from Part 7, where we had discussed about life of a scoped object. This is the last part of Dagger 2 series, where we will go through custom scopes of Dagger 2.

The scope we touched in Part 6 was @Singleton. The difference of generated code with and without @Singleton annotation is as follows

And DoubleCheck class is the one that maintains the singleton instance of the object

Now let us first look upon what is @Singleton annotation. When we look upon the source code of @Singleton annotation we get

So for creating a scope we need following 3 things

  • @Scope annotation .
  • @Retention annotation .
  • @interface definition.

If you want to know the detail usage of all these try making a simple annotation processor. Further learn more about @Retention policy from this stackoverflow question. In most basic form each annotation is looked upon by annotation processor and as per the annotation processor necessary codes are generated by the compiler.

Let us assume we know nothing of all these stuff. We only know the source code of @Singleton and we want another class or scope with our desired name in following manner

@Scope
@Retention(RUNTIME)
public @interface CustomScope {
}

Here we just changed the name Singleton to CustomScope. And let us replace all the @Singleton annotation with our @CustomScope annotation in our component and provider

@CustomScope
@Component(modules = {CoffeeProvider.class})
public interface CoffeeComponent {
void provideCoffee(RestaurantA restaurantA);
void provideCoffee(RestaurantB restaurantB);
void provideCoffee(HotelB hotelB);
}
@Module
public class CoffeeProvider {
@CustomScope
@Provides
CoffeeHelper getCoffeeHelper() {
return new CoffeeHelper();
}

}

Okay, now we created our CustomScope. Let us see if the generated code is different than that of @Singleton ( Note I am using Dagger 2.10. So, the future or previous version of Dagger 2 may have somewhat different type of generate code. But core logic will remain the same ).

This code is identical to that of @Singleton annotated usage. Meaning we will get a singleton instance of our object in our custom scope as well.

Q) Then why use CustomScope if @Singleton provides the same thing?

For this I want to rely on the answer provided in this stackoverflow

Custom scope annotations are just annotations. They can have any name. Scope annotations serve as a tool for static analysis of dependencies that modules provide and components inject.

All the codes related to this example is found on https://github.com/androidlife/get-a-fix-of-dependency/tree/customscopes_simple.

Let us move to a simple example on using custom scopes. For that let us understand our scenario

Further there is a regulation imposed on both Hotel A and Hotel B on water quantity that needs to be included while making a coffee

So the local scope and global scope of hotels will be

Now let us create our Module which provides our water

@Module
public class WaterProvider {
private int waterQuantity;
public WaterProvider(int waterQuantity) {
this.waterQuantity = waterQuantity;
}

@Provides
@Singleton
public Water getWater() {
return new Water(waterQuantity);
}

}

It is Singleton as this water quantity must be global i.e. applicable to all hotels. Now let us define our custom scopes

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface HotelAScope {
}
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface HotelBScope {
}

Now we need providers which provide CoffeeBrewer class, which is responsible for brewing coffee. For HotelA scope we need CoffeeBrewer with Espresso flavor and for HotelB scope we need CoffeeBrewer with Americano flavor.

@Module
public class CoffeeBrewerProvider {

private Coffee.Flavor flavor;
public CoffeeBrewerProvider(Coffee.Flavor flavor) {
this.flavor = flavor;
}

@Provides
public Coffee getCoffee() {
return new Coffee(flavor);
}

@Provides
@Named("ForHotelA")
@HotelAScope
public CoffeeBrewer provideCoffeeBrewerForHotelA(Water water, Coffee coffee) {
return new CoffeeBrewer(water, coffee);
}

@Provides
@Named("ForHotelB")
@HotelBScope
public CoffeeBrewer provideCoffeeBrewerForHotelB(Water water, Coffee coffee) {
return new CoffeeBrewer(water, coffee);
}
}

Here HotelA and HotelB are activities where as all the cafes are fragments. So for, global scope we create component for water in following manner

@Component(modules = {WaterProvider.class})
@Singleton
public interface WaterComponent {
}

And we define our subcomponent which we are going to use in respective cafes of Hotel A and Hotel B in following manner

@Subcomponent(modules = {CoffeeBrewerProvider.class})
@HotelAScope
public interface CoffeeComponentForHotelA {
void provideCoffeeBrewer(CafeLove cafeLove);
void provideCoffeeBrewer(CafeHeart cafeHeart);
}
@Subcomponent(modules = {CoffeeBrewerProvider.class})
@HotelBScope
public interface CoffeeComponentForHotelB {
void provideCoffeeBrewer(CoffeeTime coffeeTime);
void provideCoffeeBrewer(CoffeePeriod coffeePeriod);
}

Then let us modify our WaterComponent to provide our subcomponents as well in following manner

@Component(modules = {WaterProvider.class})
@Singleton
public interface WaterComponent {
CoffeeComponentForHotelA getCoffeeComponentForHotelA(
CoffeeBrewerProvider coffeeBrewerProvider);
CoffeeComponentForHotelB getCoffeeComponentForHotelB(
CoffeeBrewerProvider coffeeBrewerProvider);
}

We now define our global scope in our Application class in following manner

public class MainApplication extends Application {
private WaterComponent waterComponent;
@Override
public void onCreate() {
super.onCreate();
waterComponent = DaggerWaterComponent.builder().
waterProvider(new WaterProvider(20)).build();
}

public WaterComponent getWaterComponent() {
return waterComponent;
}
}

By doing this we are creating a water object with quantity 20 i.e. both hotels will now abide by the regulation. Let us now construct our subcomponents in our hotel in following manner

public class HotelA extends Hotel {
public CoffeeComponentForHotelA coffeeComponentForHotelA;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WaterComponent waterComponent = ((MainApplication) getApplication()).getWaterComponent();
coffeeComponentForHotelA = waterComponent.getCoffeeComponentForHotelA(
new CoffeeBrewerProvider(Coffee.Flavor.Espresso));
}

For Hotel B

public class HotelB extends Hotel {
public CoffeeComponentForHotelB coffeeComponentForHotelB;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WaterComponent waterComponent = ((MainApplication) getApplication()).getWaterComponent();
coffeeComponentForHotelB = waterComponent.
getCoffeeComponentForHotelB( new CoffeeBrewerProvider(Coffee.Flavor.Americano));
}

And now we can provide CoffeeBrewer object to the cafes of Hotel A in following manner

@Inject
@Named("ForHotelA")
public CoffeeBrewer coffeeBrewer;

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if(getContext() instanceof HotelA){
((HotelA)getContext()).coffeeComponentForHotelA.
provideCoffeeBrewer(this);
}
}

and for the cafes of HotelB in following manner

@Inject
@Named("ForHotelB")
public CoffeeBrewer coffeeBrewer;

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (getContext() instanceof HotelB) {
((HotelB) getContext()).coffeeComponentForHotelB.
provideCoffeeBrewer(this);
}
}

By doing this, we are creating instance of CoffeeBrewer to be alive as long as Hotel is alive. The instance of CoffeeBrewer at CafeLove and CafeHeart is same (singleton instance) and is alive as long as Hotel A operates or is alive. Similarly the instance of CoffeeBrewer at CoffeeTime and CoffeePeriod is also the same and is alive as long as Hotel B operates. Where as the water instance is alive throughout the application.

Some may find it super confusing on what is happening. I suggest them to take a different scenario than mine. Think about some real world example and try to fit the custom scopes there as I have done with two different hotels and then play around with custom scopes.

Custom scopes is simply referring to the scope of the component i.e. where it is defined. And how to keep the singleton instance as long the place( where component is defined) is alive. All the codes related to this blog post can be found in https://github.com/androidlife/get-a-fix-of-dependency/tree/scopes. This is all for this series. Keep exploring, keep learning and keep building cool stuff.

--

--

Santosh Dhakal

Android app developer having a deep interest in mobile technology be it Android or iOS. Loves to learn ,share the knowledge and build things.