Custom Scoping in Dagger 2 for Android

Ashwin Kumar
Dec 14, 2017 · 5 min read

When I started with Dagger 2 on android, I used to have a single component interface annotated with the @Component annotation, where I would define all injection points for my application (and all the dependencies, relevant and irrelevant would be available at each one of those injection points). And, when you really think about it you don’t need all the dependencies defined in your module classes (annotated with the @Module annotation) to be available at each injection point in your application. So, in this article I will demonstrate how could your split your dependencies in to different components where each component would have a different scope.

Dagger 2 allows us to define and provide dependencies based on a certain scope — Caster.IO

Now, the keyword in the above sentence is scope. What could they possibly mean by scope. Scopes in this case is pretty similar to a variable scope, where the variable is only available for use in the scope in which it was defined. The same goes for dependencies, they are only available for injection in the scope in which they were defined. So, what we will try and do is, have some dependencies scoped to a particular screen (can be an activity or fragment, but for the sake of simplicity we will deal with an activity in this article), and those particular dependencies will not be usable outside of that scope (particular screen where we intend to use them). So, let’s get started.

Step 1 — Creating a custom scope

A custom scope is nothing but an annotation. We will be creating a custom scope for the login screen, so let us name the scope to be LoginScope.

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

And that is it, we have ourselves a custom scope named LoginScope. @Scope annotation declares that the annotation being created is a custom Dagger 2 scope. And, the runtime retention policy makes our annotation to be available at runtime.

Step 2 — Creating a Dagger Module

Now, here we create a dagger 2 module named LoginModule which will serve up some objects to be injected in to our login activity. This is no different than our usual dagger 2 module. Assuming that you already know how to create a dagger 2 module, I’ll just create one without going into any details.

@Module
public class LoginModule {

@Provides
public SomeBigObject providesSomeBigObject() {
return new SomeBigObject();
}
}

So, if everything goes according to plan, we will have an instance of SomeBigObject ready for injection in our login activity.

Step 3 — Creating a God Component (optional)

Although its not necessary, its always better to have some application wide dependencies as well. So, here as an example, I will wire just the Shared Preferences (it will be a singleton) object as an application wide dependency.

Let’s start with the module first, and we will call it AppModule.

@Module
public class AppModule {
Context context; public AppModule(Context context) {
this.context = context;
}

@Provides
@Singleton
public SharedPreferences provideSharedPreferences() {
return
PreferenceManager.getDefaultSharedPreferences(context);
}
}

Now, let us move on with building the application wide dagger 2 component, and we will name it AppComponent.

@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {
}

Step 4 — Creating a Dagger 2 component for our custom scope

Now, we would have to create a dagger 2 component for our custom scope, where we will define the injection points covered inside our custom scope. Let us name this component as LoginComponent. One important thing which I would like to point out here is that the LoginComponent will be a subcomponent of our AppComponent. In doing so, our LoginComponent will inherit all dependencies from our AppComponent. Let’s go ahead and code that LoginComponent first.

@LoginScope
@Subcomponent(modules = {LoginModule.class})

public interface LoginComponent {

void inject(LoginActivity loginActivity);
}

Now, we would need to set up the LoginComponent to inherit app wide dependencies from AppComponent. Let’s do that now inside of the AppComponent interface.

@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {

ProfileComponent plus(ProfileModule profileModule);
}

Step 5 — Wiring it all up in the Application class

Now that we have everything necessary, so let us wire everything up in our app’s Application class(SampleApplication).

public class SampleApplication extends Application {

private AppComponent appComponent;
private LoginComponent loginComponent;
@Override
public void onCreate() {
super.onCreate();
appComponent = createAppComponent();
}

public AppComponent getAppComponent() {
return appComponent;
}
private AppComponent createAppComponent() {
return DaggerAppComponent.builder().
appModule(new AppModule(this)).build();
}
public LoginComponent createLoginComponent() {
loginComponent = appComponent.plus(new LoginModule())
return loginComponent;
}
public void releaseLoginComponent() {
loginComponent = null;
}
}

We only need to concentrate on the last two methods here. The first one, as the name suggests, creates a LoginComponent and returns that LoginComponent. Notice how we use the AppComponent’s plus method to create a LoginComponent, so the created LoginComponent will also contain dependencies wired with the AppComponent (pretty neat, right! Atleast according to me).

The second method is pretty interesting. When we create a custom scope in Dagger 2, we need to handle the scoping ourselves. What that basically means is that when ever we are going out of the custom scope we created (the activity getting destroyed in this case), we have to manually release the component (LoginComponent in this case) related to that particular scope. And, the releaseLoginComponent() method does exactly that.

Final Step — Using dependencies in our custom scope

public LoginActivity extends AppCompatActivity {     @Inject
SomeBigObject someBigObject;
@Inject
SharedPreferences sharedPreferences;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((SampleApplication)getActivity().getApplication())
.createLoginComponent().inject(this);

}
@Override
public void onDestroy() {
((SampleApplication)getActivity().getApplication())
.releaseLoginComponent();

super.onDestroy();
}
}

The implementation inside the Activity is pretty straight forward. I have only included the bare minimum code needed to understand custom scoping. As you can see in the code above, I inject two objects(SharedPreferences and SomeBigObject) in to the LoginActivity class using the field injection inside the onCreate() callback. And, I release the dependencies before the scope of our dependencies gets destroyed, as highlighted inside the onDestroy() callback.

Conclusion

Although custom scoping in Dagger 2 is a pretty handy tool at our disposal, I suggest not to over use it. You should always try and reuse scopes where ever it is possible and makes complete sense. Hope this was helpful, and if it was please let the community know by extending a clap. Any suggestions, discussions, or improvements are more than welcome.

Ashwin Kumar

Written by

Passionate about Computer Science and Cricket. Lifelong learner. Software developer by profession. Learning to make peace with life.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade