Feature scopes and dagger-android in an all activity architecture

Dhruv Jagetiya
AndroidPub
Published in
3 min readMar 28, 2019

--

If a feature in your android application is spread across multiple activities(versus, for instance, single activity — multiple fragment) then your AppComponent will get polluted with objects that are shared by activities in this feature.

We can choose to build a complete component-subcomponent tree without using dagger-android. If you do this, then you will have to write all your subcomponents, spam your AppComponent with builders for each of your subcomponent. We, obviously, don’t want to prove Darwin wrong and evolve back, eventually giving up the advantages of dagger-android.

However, standard setup of dagger-android is quite restrictive, allowing only Application -> Activity -> Fragment -> Sub-fragments scheme. What we want, here, is another level of subcomponent (hence, scope) between Application and Activity that will hold objects in our “Feature” scope.

The middle ground: Combination of dagger and dagger-android concepts

As proposed above, we will introduce a subcomponent for our feature:

@FeatureScope
@Subcomponent(modules = {FeatureScreensModule.class})
public abstract class FeatureSubcomponent {

@Subcomponent.Builder
public interface Builder {
FeatureSubcomponent build();
}
}

And introduce its builder in our AppComponent:

public interface AppComponent extends AndroidInjector<Application> {

FeatureSubcomponent.Builder featureComponentBuilder();

@Component.Builder
interface Builder {
@BindsInstance
AppComponent.Builder application(Application application);
AppComponent build();
}
}

But, for our activities and fragments, we will continue to use dagger-android @ContributesAndroidInjector

FeatureScreensModule will look like this:

@Module
public abstract class FeatureScreensModule {

@ContributesAndroidInjector(modules ={FeatureActivity1Module.class})
@ActivityScope
abstract FeatureActivity1 featureActivity1();

@ContributesAndroidInjector(modules = {FeatureActivity2Module.class})
@ActivityScope
abstract FeatureActivity2 featureActivity2();

}

However, if we do this, we will not be able to simply use AndroidInjection.inject(this) in our activities.

To fix this, we will need a good understanding of how dagger-android actually works and what AndroidInjection.inject(this) does. Since this article is not about that, I will simply throw out a short tutorial:

  1. @ContributesAndroidInjector creates a subcomponent.
  2. That component’s builder is then placed into a Map<Class<? extends Activity>, SubComponent.Builder>(@IntoMap annotation does this).
  3. This map is injected into your Application class(Check HasActivityInjector!) in the form of DispatchingAndroidInjector<Activity>.
  4. When you call AndroidInjection.inject(this) (this is activity), AndroidInjection takes this map from Application class and takes out SubcomponentBuilder for this activity.
  5. We use this SubcomponentBuilder to build the FeatureSubcomponent and then inject.

If you read all this and understood, awesome. So, back to our original problem:

We found that Map<Class<? extends Activity>, SubComponent.Builder> in application will not have keys for our feature activities. Instead, we will “intercept” this map in our own FeatureSubcomponent and provide it. Our subcomponent will, now, look like this:

@FeatureScope
@Subcomponent(modules = {FeatureScreensModule.class})
public abstract class FeatureSubcomponent {
public abstract DispatchingAndroidInjector<Activity> featureActivityMap(); @Subcomponent.Builder
public interface Builder {
FeatureSubcomponent build();
}
}

Now, in each of our feature activities, instead of AndroidInjection.inject(this), we will:

((YourApplication) context.getApplicationContext()).getAppComponent().featureComponentBuilder().build().featureActivityMap().inject(this);

This looks fine, but there is a major issue. We are creating a new subcomponent every time we create/restore an activity. To fix this, we need to ensure that for every “session” of our feature only one instance of subcomponent is created.

To realize this, we will keep our activity map as singleton(We don’t need to do double-locking since we will use this on main thread only):

public class FeatureStart {

private static DispatchingAndroidInjector<Activity> sFeatureInjector;

public static DispatchingAndroidInjector<Activity> getInjectorFactory(@NonNull Context context) {
if (sFeatureInjector == null) {
sFeatureInjector = getFeatureBuilder(context).build().featureActivityMap();
}
return sFeatureInjector;
}

private static FeatureSubcomponent.Builder getFeatureBuilder(@NonNull Context context) {
return ((Application)context.getApplicationContext()).getAppComponent().featureComponentBuilder();
}
}

Now, we will get our activity map from FeatureStart.getInjectorFactory() in each of our feature activities.

We can make it look a bit nicer by introducing an abstract method in our BaseActivity like:

protected abstract DispatchingAndroidInjector<Activity> getActivityMap();@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
getActivityMap().inject(this);
}

Now, each of your Feature will have it’s own FeatureBaseActivity that extends this BaseActivity.

We can make this a lot better + solve the possible cyclic dependency problem if you happen to have each of your feature in its own module by using the same idea of DispatchingAndroidInjector for our features. Maybe I will cover it in some other article.

--

--