I am using the following google sample project: https://github.com/googlesamples/android-architecture-components as a reference for my new project and having difficulties trying to add a second activity to the project.
Here is the error I get when compiling
Error:(22, 8) error: [dagger.android.AndroidInjector.inject(T)] com.apps.myapp.ui.common.MainActivity cannot be provided without an @Inject constructor or from an @Provides-annotated method. This type supports members injection but cannot be implicitly provided.
com.apps.myapp.ui.common.MainActivity is injected at
com.apps.myapp.ui.common.NavigationController.<init>(mainActivity)
com.apps.myapp.ui.common.NavigationController is injected at
com.apps.myapp.ui.addContacts.AddContactsFragment.navigationController
com.apps.myapp.ui.addContacts.AddContactsFragment is injected at
dagger.android.AndroidInjector.inject(arg0)
A binding with matching key exists in component: com.apps.myapp.di.ActivityModule_ContributeMainActivity.MainActivitySubcomponent
Here is my code
ActivityModule
@Module
public abstract class ActivityModule {
@ContributesAndroidInjector(modules = FragmentBuildersModule.class)
abstract MainActivity contributeMainActivity();
@ContributesAndroidInjector(modules = FragmentBuildersModule.class)
abstract ContactActivity contributeContactActivity();
}
AppComponent
@Singleton
@Component(modules = {
AndroidInjectionModule.class,
AppModule.class,
ActivityModule.class})
public interface AppComponent {
@Component.Builder
interface Builder {
@BindsInstance Builder application(Application application);
AppComponent build();
}
void inject(App app);
}
AppInjector
public class AppInjector {
private AppInjector() {}
public static void init(App app) {DaggerAppComponent.builder().application(app).build().inject(app);
app.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
handleActivity(activity);
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
}
private static void handleActivity(Activity activity) {
if (activity instanceof HasSupportFragmentInjector) {
AndroidInjection.inject(activity);
}
if (activity instanceof FragmentActivity) {
((FragmentActivity) activity).getSupportFragmentManager()
.registerFragmentLifecycleCallbacks(
new FragmentManager.FragmentLifecycleCallbacks() {
@Override
public void onFragmentCreated(FragmentManager fm, Fragment f,
Bundle savedInstanceState) {
if (f instanceof Injectable) {
AndroidSupportInjection.inject(f);
}
}
}, true);
}
}
}
AppModule
@Module(includes = ViewModelModule.class)
class AppModule {
@Singleton @Provides
BnderAPIService provideService() {
return new Retrofit.Builder()
.baseUrl("serverurl")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(new LiveDataCallAdapterFactory())
.build()
.create(APIService.class);
}
@Singleton @Provides
Db provideDb(Application app) {
return Room.databaseBuilder(app, Db.class,"Db.db").build();
}
@Singleton @Provides
UserDao provideUserDao(Db db) {
return db.userDao();
}
@Singleton @Provides
ContactDao provideContactDao(Db db) {
return db.contactDao();
}
}
FragmentBuildersModule
@Module
public abstract class FragmentBuildersModule {
@ContributesAndroidInjector
abstract AddContactsFragment contributeAddUserFragment();
@ContributesAndroidInjector
abstract ContactsFragment contributeContactsFragment();
@ContributesAndroidInjector
abstract ChalkboardFragment contributeChalkboardFragment();
}
Injectable
public interface Injectable {
}
ViewModelKey
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@MapKey
@interface ViewModelKey {
Class<? extends ViewModel> value();
}
ViewModelModule
@Module
abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(AddContactsViewModel.class)
abstract ViewModel bindAddContactsViewModel(AddContactsViewModel addContactsViewModel);
@Binds
@IntoMap
@ViewModelKey(ContactsViewModel.class)
abstract ViewModel bindContactsViewModel(ContactsViewModel contactsViewModel);
@Binds
@IntoMap
@ViewModelKey(ChalkboardViewModel.class)
abstract ViewModel bindChalkboardViewModel(ChalkboardViewModel chalkboardViewModel);
@Binds
abstract ViewModelProvider.Factory bindViewModelFactory(ViewModelFactory factory);
}
Application
public class App extends Application implements HasActivityInjector {
@Inject
DispatchingAndroidInjector<Activity> dispatchingAndroidInjector;
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
@Override
public void onCreate() {
super.onCreate();
if (BuildConfig.DEBUG) {
}
AppInjector.init(this);
}
@Override
public DispatchingAndroidInjector<Activity> activityInjector() {
return dispatchingAndroidInjector;
}
}
NavigationController
public class NavigationController {
private final int containerId;
private final FragmentManager fragmentManager;
@Inject
public NavigationController(MainActivity mainActivity) {
this.containerId = R.id.container;
this.fragmentManager = mainActivity.getSupportFragmentManager();
}
public void navigateToUsers() {
Log.i("TAG", "Navigate to users");
String tag = "users";
AddContactsFragment userFragment = AddContactsFragment.create();
fragmentManager.beginTransaction()
.replace(containerId, userFragment, tag)
.addToBackStack(null)
.commitAllowingStateLoss();
}
public void navigateToContacts() {
Log.i("TAG", "Navigate to contacts");
String tag = "contacts";
ContactsFragment contactsFragment = ContactsFragment.create();
fragmentManager.beginTransaction()
.add(contactsFragment, tag)
.addToBackStack(null)
.commitAllowingStateLoss();
}
public void navigateToChalkboard() {
Log.i("TAG", "Navigate to chalkboard");
String tag = "chalkboard";
ChalkboardFragment chalkboardFragment = ChalkboardFragment.create();
fragmentManager.beginTransaction()
.add(chalkboardFragment, tag)
.addToBackStack(null)
.commitAllowingStateLoss();
}
}
MainActivity
public class MainActivity extends AppCompatActivity implements LifecycleRegistryOwner, HasSupportFragmentInjector {
private final LifecycleRegistry lifecycleRegistry = new LifecycleRegistry(this);
@Inject
DispatchingAndroidInjector<Fragment> dispatchingAndroidInjector;
@Inject
NavigationController navigationController;
private Toolbar toolbar;
@Override
public LifecycleRegistry getLifecycle() {
return lifecycleRegistry;
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.setHandler(this);
binding.setManager(getSupportFragmentManager());
toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
}
@Override
public DispatchingAndroidInjector<Fragment> supportFragmentInjector() {
return dispatchingAndroidInjector;
}
static class ViewPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
public ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
@Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
@Override
public int getCount() {
return mFragmentList.size();
}
public void addFragment(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
@Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
@BindingAdapter({"handler"})
public static void bindViewPagerAdapter(final ViewPager view, final MainActivity activity) {
final ViewPagerAdapter adapter = new ViewPagerAdapter(activity.getSupportFragmentManager());
adapter.addFragment(new ChalkboardFragment(), "Chalkboard");
adapter.addFragment(new ContactsFragment(), "Contacts");
view.setAdapter(adapter);
}
@BindingAdapter({"pager"})
public static void bindViewPagerTabs(final TabLayout view, final ViewPager pagerView) {
view.setupWithViewPager(pagerView, true);
}
}
ContactActivity
public class ContactActivity extends AppCompatActivity implements LifecycleRegistryOwner, HasSupportFragmentInjector {
private final LifecycleRegistry lifecycleRegistry = new LifecycleRegistry(this);
@Inject
DispatchingAndroidInjector<Fragment> dispatchingAndroidInjector;
@Override
public LifecycleRegistry getLifecycle() {
return lifecycleRegistry;
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_contact);
if (savedInstanceState == null) {
}
}
@Override
public DispatchingAndroidInjector<Fragment> supportFragmentInjector() {
return dispatchingAndroidInjector;
}
}
This class is a perfect example of when Dagger 2 multibindings can be real lifesavers. Before to dive into code, I should make a little premise.
What is MVVM? Model-View-ViewModel architecture consists of 3 parts. The View gets user’s actions and sends to the ViewModel, or listens live data stream from the ViewModel and provides to the users. The ViewModel gets user’s actions from the View or provides data to View.
Contribute to android-mvvm-dagger-2-rxjava-example development by creating an account on GitHub.
"A binding with matching key exists in component" means that you have bound a dependency somewhere in your entire object graph but it cannot be reached from the subcomponent where it needs to be injected. Here is the javadoc:
Utility code that looks for bindings matching a key in all subcomponents in a binding graph so that a user is advised that a binding exists elsewhere when it is not found in the current subgraph. If a binding matching a key exists in a sub- or sibling component, that is often what the user actually wants to use.
For instance, assume you have two Activities, ActivityA and ActivityB. You generate subcomponents with @ContributesAndroidInjector
and bind Foo
in the ActivityA module but not the ActivityB module. If you request injection for Foo
in ActivityB with @Inject Foo foo
you will get that error message.
In your particular case, the error you are getting can be reproduced by:
Cloning the project from GitHub
git clone https://github.com/googlesamples/android-architecture-components.git`
Copy-pasting MainActivity
into a new file ContactsActivity
Modifying MainActivityModule
to look like your ActivityModule
So from this we can conclude that your ActivityModule
is problematic. The @ContributesAndroidInjector
is not as simple as it might seem. It actually means you are creating a new Dagger 2 subcomponent for the Activity you specify there (see the docs here).
A subcomponent can use bindings from a parent component but not sibling components. Your two lines for ContributesAndroidInjector
in the ActivityModule
create two sibling subcomponents: one for MainActivity
and one for ContactsActivity
.
However, NavigationController
is dependent on MainActivity
which is bound in the object graph for the MainActivity
subcomponent but not in that for the ContactsActivity
subcomponent. AddContactsFragment
has become part of the object graph for the ContactsActivity
subcomponent and doesn't have access to MainActivity
anymore. This means that when Dagger 2 tries to inject the NavigationController
inside your AddContactsFragment
it cannot provide MainActivity
as a dependency for it. This explains the "cannot provide" part of the error message.
Although it can't provide MainActivity
in that particular object graph, the AndroidInjector
does know in general about MainActivity
hence the error message "a binding key exists". What binding key is this? A key that binds MainActivity.class
to a MainActivityFactory
. Where is this key bound? In your ActivityModule
when you wrote the @ContributesAndroidInjector
for MainActivity
.
How to fix this is getting beyond scope of a StackOverflow question because it involves a lengthy refactor of the code. You would need to re-organise the object graph so that the NavigationController
no longer depends on MainActivity
. Perhaps you could make it depend on AppCompatActivity
since that is a superclass of both your Activities. You would then need to stop using ContributesAndroidInjector
and write explicit modules for your two Activities that include bindings for AppCompatActivity
.
However, for now please go back to basics and start with something easier. It is a recipe for disaster to start with a complex project without a complete understanding and just modify it hoping that it will work.
The Codepath Dagger 2 tutorial project is much easier to understand and will get you familiar with the basic concepts involved in Dagger 2. Once you are comfortable with the basic concepts and have understood dependent components and sub-components, then you can attempt a more difficult example. Good luck!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With