Search code examples
dagger-2dagger

Dagger 2.11 ContributesAndroidInjector Singleton dependency injection fails


I am exploring the new dagger.android from Dagger 2.11. I hope not to have to create custom scope annotation like @PerActivity. So far I was able to do the following:

1) Define Application scope Singletons and injecting them into activities.

2) Define Activity scope non-Singleton dependencies and injecting them into their activities using @ContributesAndroidInjector

What I cannot figure out is how to have an Application scope Singleton and Activity scope non-Singletons using it.

In the example below, I would like my Activity scope MyActivityDependencyA and MyActivityDependencyB to have access to a Singleton MyActivityService

The setup below results in:

Error:(24, 3) error: com.example.di.BuildersModule_BindMyActivity.MyActivitySubcomponent (unscoped) may not reference scoped bindings: @Singleton @Provides com.example.MyActivityService com.example.MyActivitySingletonsModule.provideMyActivityService()

Here is my setup. Note, I defined separate MyActivitySingletonsModule and MyActivityModule since I could not mix Singleton and non-Singleton dependencies in the same Module file.

@Module
public abstract class BuildersModule {
    @ContributesAndroidInjector(modules = {MyActivitySingletonsModule.class, MyActivityModule.class})
    abstract MyActivity bindMyActivity();
    }
}

and

@Module
public abstract class MyActivityModule {
    @Provides
    MyActivityDependencyA provideMyActivityDependencyA(MyActivityService myActivityService){
       return new MyActivityDependencyA(myActivityService);
    }
    @Provides
    MyActivityDependencyB provideMyActivityDependencyB(MyActivityService myActivityService) {
        return new MyActivityDependencyB(myActivityService);
    }
}

and

@Module
public abstract class MyActivitySingletonsModule {
    @Singleton
    @Provides
    MyActivityService provideMyActivityService() {
        return new MyActivityService();
    }
}

and

@Singleton
 @Component(modules = {
    AndroidSupportInjectionModule.class,
    AppModule.class,
    BuildersModule.class})

public interface AppComponent {
    @Component.Builder
    interface Builder {
        @BindsInstance
        Builder application(App application);
        AppComponent build();
    }
    void inject(App app);
}

Is it even possible to do what I am trying to do without defining custom scope annotations?

Thanks in advance!


Solution

  • Why avoid custom scopes? Custom scopes are still required for the new dagger.android dependency injection framework introduced in Dagger 2.10+.

    "My understanding is @ContributesAndroidInjector removes the need for custom annotation and I was able to prove it by using non-singletons defined in the activity scope without any issues."

    @ContributesAndroidInjector (available in v2.11) does not remove the need for custom scopes. It merely replaces the need to declare @Subcomponent classes that does not make use of @Subcomponent.Builder to inject dependencies required by the component at runtime. Take a look at the below snippet from the official dagger.android user guide about @ContributesAndroidInjector;

    "Pro-tip: If your subcomponent and its builder have no other methods or supertypes than the ones mentioned in step #2, you can use @ContributesAndroidInjector to generate them for you. Instead of steps 2 and 3, add an abstract module method that returns your activity, annotate it with @ContributesAndroidInjector, and specify the modules you want to install into the subcomponent. If the subcomponent needs scopes, apply the scope annotations to the method as well."

    @ActivityScope
    @ContributesAndroidInjector(modules = { /* modules to install into the subcomponent */ })
    abstract YourActivity contributeYourActivityInjector();
    

    The key here is "If the subcomponent needs scopes, apply the scope annotations to the method as well."

    Take a look at the following code for an overview of how to use @Singleton, @PerActivity, @PerFragment, and @PerChildFragment custom scopes with the new dagger.android injection framework.

    // Could also extend DaggerApplication instead of implementing HasActivityInjector
    // App.java
    public class App extends Application implements HasActivityInjector {
    
        @Inject
        AppDependency appDependency;
    
        @Inject
        DispatchingAndroidInjector<Activity> activityInjector;
    
        @Override
        public void onCreate() {
            super.onCreate();
            DaggerAppComponent.create().inject(this);
        }
    
        @Override
        public AndroidInjector<Activity> activityInjector() {
            return activityInjector;
        }
    }
    
    // AppModule.java
    @Module(includes = AndroidInjectionModule.class)
    abstract class AppModule {
        @PerActivity
        @ContributesAndroidInjector(modules = MainActivityModule.class)
        abstract MainActivity mainActivityInjector();
    }
    
    // AppComponent.java
    @Singleton
    @Component(modules = AppModule.class)
    interface AppComponent {
        void inject(App app);
    }
    
    // Could also extend DaggerActivity instead of implementing HasFragmentInjector
    // MainActivity.java
    public final class MainActivity extends Activity implements HasFragmentInjector {
    
        @Inject
        AppDependency appDependency; // same object from App
    
        @Inject
        ActivityDependency activityDependency;
    
        @Inject
        DispatchingAndroidInjector<Fragment> fragmentInjector;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            AndroidInjection.inject(this);
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main_activity);
    
            if (savedInstanceState == null) {
                addFragment(R.id.fragment_container, new MainFragment());
            }
        }
    
        @Override
        public final AndroidInjector<Fragment> fragmentInjector() {
            return fragmentInjector;
        }
    }
    
    // MainActivityModule.java
    @Module
    public abstract class MainActivityModule {
        @PerFragment
        @ContributesAndroidInjector(modules = MainFragmentModule.class)
        abstract MainFragment mainFragmentInjector();
    }
    
    // Could also extend DaggerFragment instead of implementing HasFragmentInjector
    // MainFragment.java
    public final class MainFragment extends Fragment implements HasFragmentInjector {
    
        @Inject
        AppDependency appDependency; // same object from App
    
        @Inject
        ActivityDependency activityDependency; // same object from MainActivity
    
        @Inject
        FragmentDependency fragmentDepency; 
    
        @Inject
        DispatchingAndroidInjector<Fragment> childFragmentInjector;
    
        @SuppressWarnings("deprecation")
        @Override
        public void onAttach(Activity activity) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
                // Perform injection here before M, L (API 22) and below because onAttach(Context)
                // is not yet available at L.
                AndroidInjection.inject(this);
            }
            super.onAttach(activity);
        }
    
        @Override
        public void onAttach(Context context) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                // Perform injection here for M (API 23) due to deprecation of onAttach(Activity).
                AndroidInjection.inject(this);
            }
            super.onAttach(context);
        }
    
        @Override
        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
                                 Bundle savedInstanceState) {
            return inflater.inflate(R.layout.main_fragment, container, false);
        }
    
        @Override
        public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
    
            if (savedInstanceState == null) {
                addChildFragment(R.id.child_fragment_container, new MainChildFragment());
            }
        }
    
        @Override
        public final AndroidInjector<Fragment> fragmentInjector() {
            return childFragmentInjector;
        }
    }
    
    // MainFragmentModule.java
    @Module
    public abstract class MainFragmentModule {
        @PerChildFragment
        @ContributesAndroidInjector(modules = MainChildFragmentModule.class)
        abstract MainChildFragment mainChildFragmentInjector();
    }
    
    // MainChildFragment.java
    public final class MainChildFragment extends Fragment {
    
        @Inject
        AppDependency appDependency; // same object from App
    
        @Inject
        ActivityDependency activityDependency; // same object from MainActivity
    
        @Inject
        FragmentDependency fragmentDepency; // same object from MainFragment
    
        @Inject
        ChildFragmentDependency childFragmentDepency;
    
        @Override
        public void onAttach(Context context) {
            AndroidInjection.inject(this);
            super.onAttach(context);
        }
    
        @Override
        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
                                 Bundle savedInstanceState) {
            return inflater.inflate(R.layout.main_child_fragment, container, false);
        }
    }
    
    // MainChildFragmentModule.java
    @Module
    public abstract class MainChildFragmentModule {
    }
    
    // PerActivity.java
    @Scope
    @Retention(RetentionPolicy.RUNTIME)
    public @interface PerActivity {
    }
    
    // PerFragment.java
    @Scope
    @Retention(RetentionPolicy.RUNTIME)
    public @interface PerFragment {
    }
    
    // PerChildFragment.java
    @Scope
    @Retention(RetentionPolicy.RUNTIME)
    public @interface PerChildFragment {
    }
    
    // AppDependency.java
    @Singleton
    public final class AppDependency {
        @Inject
        AppDependency() {
        }
    }
    
    // ActivityDependency.java
    @PerActivity
    public final class ActivityDependency {
        @Inject
        ActivityDependency() {
        }
    }
    
    // FragmentDependency.java
    @PerFragment
    public final class FragmentDependency {
        @Inject
        FragmentDependency() {
        }
    } 
    
    // ChildFragmentDependency.java
    @PerChildFragment
    public final class ChildFragmentDependency {
        @Inject
        ChildFragmentDependency() {
        }
    }
    

    For a complete dagger.android 2.11 setup guide using @ContributesAndroidInjector and custom scopes mentioned above, read this article.