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!
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.