0

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?

Android-rocks
  • 89
  • 2
  • 9
  • As a follow up to [your other question](https://stackoverflow.com/q/47444206/1426891), I appreciate that this has more specific code, but "is it possible" doesn't tell us whether you've tried your own code. Have you? Are you getting an error message? – Jeff Bowman Nov 23 '17 at 16:25
  • I have updated my question. Thanks for the suggestion. – Android-rocks Nov 23 '17 at 16:31
  • Ah! Much better, thank you. Before I answer, is there a reason you made your TasksFragment activity-scoped, rather than fragment-scoped or entirely unscoped? – Jeff Bowman Nov 23 '17 at 16:37
  • Yeah. As I am dynamically adding fragment to activity instead of static way, I am instantiating it through dagger. For reference, you can use the project mentioned in the above link. – Android-rocks Nov 23 '17 at 16:42

1 Answers1

3

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.
Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • Thanks for the answer. But confusion from your answer is, even the dagger uses same constructor what android uses to instantiate the fragment. so what is the main difference/conflict happens here? – Android-rocks Nov 23 '17 at 19:19
  • 1
    As in my other answer, when Dagger calls an `@Inject` constructor, it also automatically attempts to populate `@Inject` fields and call `@Inject` methods. This is a problem if you ask your Activity component to inject the Fragment, as you do above; the Fragment component that dagger.android creates is the correct component, and it is not designed to be created or to act as a factory for your Fragment. Even if it were easy, at best this would be unnecessary work, because the Fragment instance's `onAttach` will always inject and will always use the right component. – Jeff Bowman Nov 23 '17 at 20:13
  • As I am using Lazy variable here, the fragment instance will not be created until the lazy.get() method is called(constructor injection). And field injection will not happen until the fragmentmanager attaches the fragment to the activity. So before attaching the fragment to the activity, I will check whether the fragment was already attached before or not(which actually restricts in doing field injection) – Android-rocks Nov 23 '17 at 20:25
  • 1
    Let me be direct: **`@Inject` annotating your Fragment constructor is a bad idea. Please do not do it.** – Jeff Bowman Nov 23 '17 at 20:29
  • Ok!! Thanks man. Still confused to understand. But I will try to adopt this approach. – Android-rocks Nov 23 '17 at 20:38
  • According to android approach the above answer is fine. But it is actually concept of subcomponent. Why it is giving error like that? – Android-rocks Nov 23 '17 at 20:45
  • Thanks man... I got the concept...You saved me from dagger injection.:) – Android-rocks Nov 24 '17 at 08:12