Reputation: 1
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
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
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