Android-rocks
Android-rocks

Reputation: 109

Dagger Scope conflict

Reprository : https://github.com/googlesamples/android-architecture

Branch - todo-mvp-dagger

It is found that TaskFragment is injected using contructorinjection

For instance : In the TasksModule, I want to add another module for task fragment like below for field Injection in the TaskFragment

@Module
class TasksModule{
       @Fragmentscoped
       @contributesandroidinjector(modules = AnotherModule.class)
      abstract TasksFragment tasksFragment();
}

@Module
public class AnotherModule {

    @Provides
    @FragmentScoped
    static Calendar getCalendar() {
        return Calendar.getInstance();
    }

}


@activityscoped
public class TasksFragment extends DaggerFragment implements TasksContract.View {
     @Inject
    Calendar calendar;//Field injection

     @Inject
    TasksFragment(){
    }
}

Activity :

public class TasksActivity extends DaggerAppCompatActivity {

    @Inject
    Lazy<TasksFragment> taskFragmentProvider;


....
}

I am getting the error as :

Error:(34, 8) error: [dagger.android.AndroidInjector.inject(T)] java.util.Calendar cannot be provided without an @Provides- or @Produces-annotated method.
java.util.Calendar is injected at
com.example.android.architecture.blueprints.todoapp.tasks.TasksFragment.calendar
dagger.Lazy<com.example.android.architecture.blueprints.todoapp.tasks.TasksFragment> is injected at
com.example.android.architecture.blueprints.todoapp.tasks.TasksActivity.taskFragmentProvider
com.example.android.architecture.blueprints.todoapp.tasks.TasksActivity is injected at
dagger.android.AndroidInjector.inject(arg0)
A binding with matching key exists in component: com.example.android.architecture.blueprints.todoapp.tasks.TasksModule_TasksFragment.TasksFragmentSubcomponent

Am I missing anything here regarding injection?

Upvotes: 0

Views: 1655

Answers (1)

Jeff Bowman
Jeff Bowman

Reputation: 95654

To answer your question directly, you are evidently trying to instantiate your TasksFragment from within your ActivityComponent. However, you have bound your Calendar in @FragmentScoped scope within the fragment-specific subcomponent that dagger.android creates for you. This means that Calendar is available only from within your Fragment (and other objects that your Fragment accesses), not from within your Activity.

If you want the right scope and injecting component, you'd use the Fragment's component, which has a generated name and no Fragment-constructing method: It's not designed to be called that way.

The easy answer: don't rely on constructor injection here. You should be calling new on your Fragment, because that's what Android does; you are required to have a public parameterless constructor specifically for this purpose, and Android will not inject your Fragment upon construction. Though there aren't any special Dagger rules about constructor injection and field injection here, this is a constraint of the Android system and its necessary ability to recreate Fragment instances for you.

Instead, by extending DaggerFragment, you are instructing Dagger to inject your Fragment in its onAttach method, which is the right way to do it, and the way that dagger.android is designed for. If you were to inject the Fragment earlier, manually or automatically, then those @Inject fields would be replaced and reinjected when the Fragment is attached...and your Fragment would have differing behavior depending on whether Android creates the object for you automatically, or whether you do so yourself.

Other notes:

  • It's not important to inject your Fragment itself with @FragmentScoped, because Dagger will create a new instance of your component whenever your fragment is attached, and if any of your Fragment's dependencies inject TasksFragment, they will get the correct Fragment instance due to the instance bound in the Subcomponent.Builder that dagger.android generates for you.
  • Please don't refer to @Inject Lazy<TasksFragment> taskFragmentProvider as a Provider: Unlike a general Provider, a Lazy will always return the same instance, even if the object is scopeless.
  • You can interact with your Fragment after creating it and before attaching it, but the only thing you should expect to do is to assign it a Bundle containing the Fragment's instance arguments. This allows Android the flexibility it needs to create and recreate the object, and will supply your Bundle to the onAttach method where you can use those Bundle arguments with full access to your injected objects.

Upvotes: 3

Related Questions