I am following coding with mitch's dagger 2 course from youtube.I am confused about something.Here is my classes:
AppComponent.class
#Singleton
#Component(
modules = {
AndroidSupportInjectionModule.class,
ActivityBuildersModule.class,
AppModule.class,
ViewModelFactoryModule.class,
}
)
public interface AppComponent extends AndroidInjector<BaseApplication> {
SessionManager sessionManager();
#Component.Builder
interface Builder{
#BindsInstance
Builder application(Application application);
AppComponent build();
}
}
ActivityBuildersModule
#Module
public abstract class ActivityBuildersModule {
#ContributesAndroidInjector(
modules = {AuthViewModelsModule.class,
AuthModule.class
}
)
abstract AuthActivity contributeAuthActivity();
#ContributesAndroidInjector
abstract MainActivity contributeMainActivity();
}
BaseActivity
public abstract class BaseActivity extends DaggerAppCompatActivity {
private static final String TAG = "BaseActivity";
#Inject
public SessionManager sessionManager;//confused here
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
subscribeObservers();
}
private void subscribeObservers() {
sessionManager.getAuthUser().observe(this, userAuthResource -> {
if (userAuthResource != null) {
switch (userAuthResource.status) {
case LOADING: {
break;
}
case AUTHENTICATED: {
Log.d(TAG, "subsrcibeObservers: LOGIN SUCCESS:" + userAuthResource.data.getEmail());
break;
}
case ERROR: {
Toast.makeText(this, userAuthResource.message, Toast.LENGTH_SHORT).show();
break;
}
case NOT_AUTHENTICATED: {
navLoginScreen();
break;
}
}
}
});
}
private void navLoginScreen(){
Intent intent = new Intent(this, AuthActivity.class);
startActivity(intent);
finish();
}
}
MainActivity
public class MainActivity extends BaseActivity {
private static final String TAG = "MainActivity";
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
SessionManager
#Singleton
public class SessionManager {
private static final String TAG = "SessionManager";
private MediatorLiveData<AuthResource<User>> cachedUser = new MediatorLiveData<>();
#Inject
public SessionManager() {
}
public void authenticaWithId(final LiveData<AuthResource<User>> source) {
if (cachedUser != null) {
cachedUser.setValue(AuthResource.loading(null));
cachedUser.addSource(source, userAuthResource -> {
cachedUser.setValue(userAuthResource);
cachedUser.removeSource(source);
});
}
}
public void logOut(){
Log.d(TAG, "logOut: logging out...");
cachedUser.setValue(AuthResource.logout());
}
public LiveData<AuthResource<User>> getAuthUser(){
return cachedUser;
}
}
BaseApplication
public class BaseApplication extends DaggerApplication {
#Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
return DaggerAppComponent.builder().application(this).build();
}
}
My problem is #Inject SessionManager sessionManager that statement in the BaseActivity.In the ActivityBuildersModule class, we only annotated MainActivity as a subcomponent, not the BaseActivity.Since the MainActivity is a subcomponent it can access dependencies of the AppComponent.So how we get accessed that SessionManager in the Base Activity or we get accessed that object because MainActivity is derived from BaseActivity?
Dagger will inject any annotated fields and methods of the declared parameter type and its supertypes. You can also read about it on the JavaDoc:
Members-injection methods
Members-injection methods have a single parameter and inject dependencies into each of the Inject-annotated fields and methods of the passed instance.
[...]
A note about covariance
While a members-injection method for a type will accept instances of its subtypes, only Inject-annotated members of the parameter type and its supertypes will be injected;
So if you have a SubActivity < BaseActivity < AppCompatActivity then declaring the method as inject(activity: BaseActivity) would only inject fields of BaseActivity and any supertypes, wheras inject(activity: SubActivity) will inject SubActivity as well as any parent/supertypes (BaseActivity in this example, your observed behavior).
Related
I am sort of new to Dagger and still learning it. According to the tutorials and blogs I read, currently Android does not have a way of injecting dependencies into ViewModels hence we need to use a custom ViewModelProvider.Factory, that said, I managed to get my hands on this one
public class ViewModelProviderFactory implements ViewModelProvider.Factory {
private static final String TAG = ViewModelProviderFactory.class.getSimpleName();
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;
#Inject
public ViewModelProviderFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
this.creators = creators;
}
#Override
public <T extends ViewModel> T create(Class<T> modelClass) {
Provider<? extends ViewModel> creator = creators.get(modelClass);
if (creator == null) {
for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : creators.entrySet()) {
if (modelClass.isAssignableFrom(entry.getKey())) {
creator = entry.getValue();
break;
}
}
}
if (creator == null) {
throw new IllegalArgumentException("unknown model class " + modelClass);
}
try {
return (T) creator.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
It works, it has worked for many of my use cases until now. Originally I had to an instance of a ViewModel with something like this
public AFragment extends BaseFragment{
#Inject
ViewModelProviderFactory providerFactory;
private MyViewModel viewModel;
MyViewModel getViewModel(){
return ViewModelProviders.of(this, providerFactory).get(MyViewModel.class);
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = getViewModel();
tokenAuthenticator.setAuthenticatorListener(this);
}
}
But as the project grew I realized this was not neat, I had to do this in all my fragments so I opted for a different approach, I wanted to instantiate my ViewModel in my BaseFragment instead and I did this
public abstract class BaseFragment<T extends BaseViewModel, E extends ViewDataBinding> extends DaggerFragment implements TokenAuthenticator.AuthenticatorListener {
private static final String TAG = BaseFragment.class.getSimpleName();
public E binding;
public final CompositeDisposable disposables = new CompositeDisposable();
public T viewModel;
#Inject
ViewModelProviderFactory providerFactory;
private int layoutId;
/**
* #return view model instance
*/
public T getViewModel() {
final Type[] types = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments();
return ViewModelProviders.of(this, providerFactory).get((Class<T>)types[0]);
}
}
This gives me compile error
A binding with matching key exists in component: xxx.xxx.core.base.dagger.builders.FragmentBuilderModule_ContributeDeliveryPlanFragment.DeliveryPlanFragmentSubcomponent
.
.
.
A binding with matching key exists in component: xxx.xxx.core.base.dagger.builders.FragmentBuilderModule_ContributePayWithMtmMobileMoneyFragment.PayWithMtmMobileMoneyFragmentSubcomponent
java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
xxx.xxx.core.base.ViewModelProviderFactory(creators)
xxx.xxx.core.base.ViewModelProviderFactory is injected at
mika.e.mikaexpressstore.core.base.BaseFragment.providerFactory
xxx.xxx.xxx.xxx.cashondelivery.CashOnDeliveryFragment is injected at
dagger.android.AndroidInjector.inject(T) [xxx.xxx.core.base.dagger.component.AppComponent → xxx.xxx.core.base.dagger.builders.FragmentBuilderModule_ContributeCashOnDeliveryFragment.CashOnDeliveryFragmentSubcomponent]
2 errors
From the error message I can tell Dagger is complaining the ViewModelProviderFactory is being injected in the base but used in the child, I need help, is there a way to make this work? surely I want to reduce on boilerplate and repetitive code.
I finally fixed it, not how I wanted but better than having to instantiate each viewmodel from the child class. After reading this answer I came to a realization that this was not possible, so instead I removed #Inject annotation from ViewModelProviderFactory in my BaseFragment and it looked like
public abstract class BaseFragment<T extends BaseViewModel, E extends ViewDataBinding> extends DaggerFragment implements TokenAuthenticator.AuthenticatorListener {
private static final String TAG = BaseFragment.class.getSimpleName();
public E binding;
public final CompositeDisposable disposables = new CompositeDisposable();
public T viewModel;
#Inject
private ViewModelProviderFactory providerFactory;
private int layoutId;
/**
* #return view model instance
*/
public T getViewModel() {
final Type[] types = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments();
return ViewModelProviders.of(this, providerFactory).get((Class<T>)types[0]);
}
#MainThread
protected final void setProviderFactory(ViewModelProviderFactory providerFactory) {
this.providerFactory = providerFactory;
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = getViewModel();
}
}
And injected the provider from the child fragments instead then called the setter from the BaseFragment
public AFragment extends BaseFragment{
#Inject
ViewModelProviderFactory providerFactory;
private MyViewModel viewModel;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
setLayoutId(R.layout.layout_layout);
setProviderFactory(providerFactory);
super.onCreate(savedInstanceState);
}
}
Key here is to call setProviderFactory(provider) before super.onCreate(savedInstanceState) because when super onCreate is called the provider should not be null, it should be set and ready to create the ViewModel
Using the google sample GithubsampleBrowser I have become stuck on trying to inject the ViewModelProvider.Factory.
When comparing with the sample, I see it goes GithubViewModelFactory fine but mine never does and i'm not sure what I am missing. Hopefully it is something very simple and presumably is because I am using androidx components instead.
Main Activity:
public class MainActivity extends AppCompatActivity implements HasSupportFragmentInjector {
#Inject
DispatchingAndroidInjector<Fragment> dispatchingAndroidInjector;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
public DispatchingAndroidInjector<Fragment> supportFragmentInjector() {
return dispatchingAndroidInjector;
}
}
CategoryFragment:
public class CategoryFragment extends Fragment implements Injectable {
#Inject
ViewModelProvider.Factory viewModelFactory;// <-- remains null
AppInjector:
public class AppInjector {
private AppInjector() {}
public static void init(CrosscareApp crossCareApp) {
DaggerAppComponent.builder().application(crossCareApp)
.build().inject(crossCareApp);
crossCareApp
.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
........
}
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);
}
}
}
AppComponent:
#Singleton
#Component(modules = {
AndroidSupportInjectionModule.class,
AppModule.class,
MainActivityModule.class
})
public interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance Builder application(Application application);
AppComponent build();
}
void inject(CrosscareApp crosscareApp);
}
AppModule:
#Module(includes = ViewModelModule.class)
class AppModule {
#Singleton #Provides
CrosscareService provideCrossCareService() {
return new Retrofit.Builder()
..........
}
ViewModelModule:
#Module
abstract class ViewModelModule {
#Binds
#IntoMap
#ViewModelKey(CategoryViewModel.class)
abstract ViewModel bindCategoryViewModel(CategoryViewModel categoryViewModel);
#Binds
abstract ViewModelProvider.Factory bindViewModelFactory(CrosscareViewModelFactory factory);
}
CrosscareViewModel:
#Singleton
public class CrosscareViewModelFactory implements ViewModelProvider.Factory {
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;
#Inject
public CrosscareViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
this.creators = creators;
}
Greatly appreciate any help.
Well after all that, I was missing one thing. In the manifest, I needed:
android:name=".CrosscareApp"
In the sample, it was
android:name=".GithubApp"
Presumably the file at the root of the directory.
And then it works.
I am trying to use Dagger 2 to inject my application class, MyApplication as I use it in various places. This is my setup using Dagger 2.11
MyApplication.java
public class MyApplication extends Application implements HasActivityInjector {
#Inject
DispatchingAndroidInjector<Activity> dispatchingAndroidInjector;
#Override
public void onCreate() {
super.onCreate();
AppInjector.init(this);
}
#Override
public DispatchingAndroidInjector<Activity> activityInjector() {
return dispatchingAndroidInjector;
}
}
AppInjector.java
public class AppInjector {
public static void init(MyApplication application){
//Initialize dagger and inject the application
DaggerAppComponent.builder().application(application).build().inject(application);
application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
#Override
public void onActivityCreated(Activity activity, Bundle aBundle) {
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 aBundle) {
}
#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) {
Log.i("LifecycleCallbacks", "injected:" + f);
AndroidSupportInjection.inject(f);
}
}
}, true);
}
}
AppComponent.java
#Singleton
#Component(modules = {
AndroidInjectionModule.class,
ActivityBuilder.class,
AppModule.class
})
public interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance Builder application(Application application);
AppComponent build();
}
void inject(MyApplication application);
}
However, every time I try use #Inject MyApplication application in a constructor, dagger throws an error that it has no way to provide it without an #Provides
Furthure more, I am not sure I should be using the Application everywhere, and rather only its Context? If so, how would I provide the Context?
Have a look at your Builder...
#Component.Builder
interface Builder {
--> #BindsInstance Builder application(Application application);
AppComponent build();
}
All Dagger knows about is your Application, you're never mentioning MyApplication, hence injecting it will fail.
I don't know why you'd have to inject MyApplication specifically, but the easiest solution would be to change it to bind your MyApplication instead...
#Component.Builder
interface Builder {
#BindsInstance Builder application(/** --> */ MyApplication application);
AppComponent build();
}
Then Dagger knows about MyApplication but not of Application. To fix this, you can just add a module that binds the other types which is easy enough because you have the subtype...e.g.
#Module interface AppModule { // could also be an abstract class
#Binds Application bindApplication(MyApplication application);
// if you also want to bind context
#Binds Context bindContext(MyApplication application);
}
And just add this module to your component.
Sorry for my english, now i begin learn dagger2 and i cant understand why i have error:
Error:(9, 10) error:
test.dagger.dagger.modules.MainActivityPresenterModule cannot be
provided without an #Inject constructor or from an #Provides- or
#Produces-annotated method.
test.dagger.dagger.modules.MainActivityPresenterModule is injected at
test.dagger.view.activitys.MainActivity.mainActivityPresenterModule
test.dagger.view.activitys.MainActivity is injected at
test.dagger.dagger.components.AppComponent.injectMainActivity(mainActivity)
App
public class App extends Application {
private static AppComponent component;
#Override
public void onCreate() {
super.onCreate();
component = DaggerAppComponent.create();
}
public static AppComponent getComponent() {
return component;
}
}
AppComponent
#Component(modules = {MainActivityPresenterModule.class})
public interface AppComponent {
void injectMainActivity(MainActivity mainActivity);
}
MainActivityPresenterModule
#Module
public class MainActivityPresenterModule {
#Provides
MainActivityPresenter provideActivityPresenter(NetworkUtils networkUtils) {
return new MainActivityPresenter(networkUtils);
}
#Provides
NetworkUtils provideNetworkUtils(){
return new NetworkUtils();
}
}
NetworkUtils
public class NetworkUtils {
public boolean isConnection() {
return true;
}
}
MainActivityPresenter
public class MainActivityPresenter {
NetworkUtils networkUtils;
public MainActivityPresenter(NetworkUtils networkUtils) {
this.networkUtils = networkUtils;
}
public void getUser(){
if(networkUtils.isConnection()) {
Log.e("getUser", "getUser");
} else {
Log.e("no internet", "no internet connection");
}
}
}
MainActivity
public class MainActivity extends AppCompatActivity {
#Inject
MainActivityPresenterModule mainActivityPresenterModule;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
App.getComponent().injectMainActivity(MainActivity.this);
}
}
You can inject only the things that are provided in the classes annotated with #Module (only methods inside that module which are annotated with #Provides). So, you can do #Inject MainActivityPresenter presenter, for instance, not try to inject the whole module, like you tried to do. Modules should be registered on Dagger initialisation, like this (in App#onCreate)
component = DaggerAppComponent.builder()
.mainActivityPresenterModule(MainActivityPresenterModule())
.build()
In MainActivity you only need to call inject to be able to inject your #Inject MainActivityPresenter presenter or any other injects defined in the module, like so:
#Inject MainActivityPresenter presenter
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
(application as App).component.inject(this)
// after #inject(this) above you can start using your injections:
presenter.getUser()
}
Sorry, I wrote code snippets in Kotlin as it was much less to write that way, hopefully you get the idea how it looks in Java.
i'm trying to use Dagger 2 into my apps but i'm having some problems regarding the entities repository and i haven't figured it out what i'm missing.
Here is my Application Component:
#Singleton
#Component(
modules = {
AndroidModule.class,
RepositoryModule.class
}
)
public interface ApplicationComponent {
void inject(AndroidApplication app);
IDependenceyRepository dependencyRepository();
}
My modules:
#Module
public class RepositoryModule {
#Provides #Singleton IDependenceyRepository provideDependendencyRepository(Context context) {
return new DependencyRepository(context);
}
}
#Module
public class AndroidModule {
private final AndroidApplication app;
public AndroidModule(AndroidApplication app) {
this.app = app;
}
#Provides #Singleton Context provideApplicationContext() {
return app;
}
}
My AndroidApplication:
public class AndroidApplication extends Application {
private ApplicationComponent component;
#Override
public void onCreate() {
super.onCreate();
setupGraph();
}
private void setupGraph() {
component = DaggerApplicationComponent.builder()
.androidModule(new AndroidModule(this))
.repositoryModule(new RepositoryModule())
.build();
component.inject(this);
}
public ApplicationComponent component() {
return component;
}
}
And here is my activity:
public class MainActivity extends Activity {
#Inject
IDependenceyRepository repository;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.testRepository();
}
private void testRepository() {
Dependency dep = new Dependency();
dep.setDescription("XXX");
dep.setName("AAAA");
try {
repository.save(dep);
Log.d("", repository.queryAll().get(0).getName());
} catch (SQLException e) {
e.printStackTrace();
}
}
What happens is that i'm getting a null pointer exception into the repository. He is not being injected.
If i add this line though:
repository = ((AndroidApplication) getApplication()).component().dependencyRepository();
It works, but the point of the DI it's not to have to worry about this, or am im wrong about that?
I've tried some other example and tutorials but haven't managed to solve my problem.
Thanks in advance,
david.mihola's comment is correct: in order to be able to have #Inject fields initialized, you need to have a method (typically void inject(MyClass)) in your component.
It's worth noting (not specifically for your question, but it could come up) that if you have a base class:
public class BaseActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((AndroidApplication) getApplication()).component().inject(this);
}
}
and in your component:
void inject(BaseActivity);
Injecting into a subclass that has it's own #Inject fields won't work:
public class ActualActivity extends BaseActivity {
#Inject IDependenceyRepository repo;
}
This is because Dagger needs to know the concrete classes that will be injected at compile time, not runtime (like Dagger 1 could do).