0
votes

I am trying to understand dagger.android framework that is included in Dagger 2.11. I wrote a sample code that implements some scopes, @Singleton, @ActivityScope and @FragmentScope.

My Activity has a fragment, and fragment has a Toy object. I want that MainFragment belong to Activity Scope and Toy object belong to Fragment scope.

But I have an error, Could you help me please? What is the problem? :

Error:(22, 8) error: [dagger.android.AndroidInjector.inject(T)] com.example.user.daggerapplication4.Models.Toy cannot be provided without an @Provides-annotated method. com.example.user.daggerapplication4.Models.Toy is injected at com.example.user.daggerapplication4.ui.MainFragment.toy com.example.user.daggerapplication4.ui.MainFragment is injected at com.example.user.daggerapplication4.ui.MainActivity.injectedFragment com.example.user.daggerapplication4.ui.MainActivity is injected at dagger.android.AndroidInjector.inject(arg0) A binding with matching key exists in component: com.example.user.daggerapplication4.ui.MainActivityModule_BindMainFragment.MainFragmentSubcomponent

AppComponent and Module :

@Singleton
@Component(modules = {
        AndroidInjectionModule.class,
        AppModule.class,
        ActivityBuilder.class
})
public interface AppComponent extends AndroidInjector<DaggerSample4Application> {
    @Component.Builder
    abstract class Builder extends AndroidInjector.Builder<DaggerSample4Application> {}
}

@Module
public class AppModule {

}

@Module
public abstract class ActivityBuilder {

    @ActivityScoped
    @ContributesAndroidInjector(modules = MainActivityModule.class)
    abstract MainActivity bindMainActivity();
}

Scopes :

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface FragmentScoped {}

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScoped {}

ActivityModule and FragmentModule

@Module()
public abstract class MainActivityModule {
    @FragmentScoped
    @ContributesAndroidInjector(modules = MainFragmentModule.class)
    abstract MainFragment bindMainFragment();
}

@Module
public class MainFragmentModule {

    @Provides
    @FragmentScoped
    Toy provideToy()
    {
        return new Puzzle();
    }
}

Model Classes:

public interface Toy {
    public String play();
}

public class Puzzle implements Toy {
    @Override
    public String play() {
        Log.v("DaggerSample","Play with Puzzle");
        return "Play with Puzzle";
    }
}

MainActivity and MainFragment

public class MainActivity extends DaggerAppCompatActivity {
    @Inject
    MainFragment injectedFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        MainFragment mainFragment = (MainFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
       // injectedFragment = new MainFragment();
        if (mainFragment == null) {
            mainFragment = injectedFragment;
            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
            transaction.add(R.id.contentFrame, mainFragment);
            transaction.commit();
        }
    }
}

public class MainFragment extends DaggerFragment {
    private Button btnBuy;
    private TextView textResult;

    @Inject
    Toy toy;

    @Inject
    public MainFragment()
    {

    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.fragment_main, container, false);
        btnBuy = root.findViewById(R.id.btnBuy);
        textResult = root.findViewById(R.id.textRresult);

        btnBuy.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                showMessage(toy.play());
            }
        });

        return root;
    }

    public void showMessage(String message) {
        textResult.setText(message);
    }
}

If you want to investigate the code, you can access with this link

2
Seems like you're trying to inject the fragment with your activitycomponent? - David Medenjak
Yes, I am trying to inject MainFragment by MainActivitySubComponent, inject Toy Model by MainFragmentSubComponent. They have own scope. - Eray Dogan
Remove the @Inject from the fragments constructor. Dagger will try to inject the fields after creating the fragment, all from your ActivityComponent, hence your error. I suggest you create the fragment without dagger - David Medenjak
If I remove @Inject annatation from constructor and injectedFragment field on MainActivity. it worked. is it means, there is no way for injecting fragment on activity scope and injecting a field on fragment scope at the same time? - Eray Dogan
Not directly. I did not yet do something like this, but you'd have to create the fragment using it's own subcomponent and include this factory somehow in your activity component. - David Medenjak

2 Answers

0
votes

I think while using interface it is better to use @Binds. Try the method below.

@Module
public class MainFragmentModule {
    @FragmentScoped
     @Binds
      public abstract Toy bindToy(Puzzle  puzzle);
}



public class Puzzle implements Toy {
    @Inject
    public Puzzle(){
    }
    @Override
    public String play() {
        Log.v("DaggerSample","Play with Puzzle");
        return "Play with Puzzle";
    }
}
0
votes

You have a few errors to fix in your code to get your project to compile. But first, rule of thumb for efficient Dagger - always prefer making your modules as abstract classes with abstract @Binds methods or if not possible with static @Provides methods. This means you need to make AppModule an abstract class, otherwise your project won't compile as per the code you posted here.

The main reason why your code doesn't compile is because Puzzle doesn't have a constructor that is annotated with @Inject:

public class Puzzle implements Toy {
    @Inject // Add this
    public Puzzle() {
    }

    @Override
    public String play() {
        Log.v("DaggerSample","Play with Puzzle");
        return "Play with Puzzle";
    }
}

Next, you need to make the following changes to this module:

@Module
public class MainFragmentModule { // Make this class abstract

    @Provides // Change this to @Binds instead
    @FragmentScoped
    Toy provideToy() // Change this method to look like this below method
    {
        return new Puzzle();
    }

    @Binds
    @FragmentScoped
    abstract Toy bindPuzzle(Puzzle puzzle);
}

If you have other classes that implement Toy interface that you want to inject, you'll have to use qualifiers (@Named annotation) to tell Dagger which implementation to inject.

You cannot inject a fragment to the activity that is hosting it. Instead, you must create the fragment and add it using the fragment manager instead.

public class MainActivity extends DaggerAppCompatActivity {
    @Inject // Remove this
    MainFragment injectedFragment; // And this if you don't use this field

You can't annotate the fragment constructor with @Inject. Fragment is an Android Component and Android Components cannot be injected via constructor injection. The only way you can inject Android Components is via member injection, which is already done for you if your fragment inherits from DaggerFragment. Notice that if you're using support library Fragments, make sure to use DaggerFragment variant which is from the support package.

You haven't included your DaggerSample4Application code so I can't tell if you're doing something wrong there, but the main point is that this class needs to extend DaggerApplication and implement some methods. I have a complete working sample that you can check out: https://github.com/Nimrodda/dagger-androidinjector It's the source code for an article I wrote about Dagger Android injection https://android.jlelse.eu/android-and-dagger-2-10-androidinjector-5e9c523679a3 I highly recommend you check it out to get better understanding.