Eray Dogan
Eray Dogan

Reputation: 1

Dagger2.11 cannot be provided without an @Provides-annotated method

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

Upvotes: 0

Views: 3933

Answers (2)

Nimrod Dayan
Nimrod Dayan

Reputation: 3100

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.

Upvotes: 0

Ranjith KP
Ranjith KP

Reputation: 810

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";
    }
}

Upvotes: 0

Related Questions