CustomView dependency injection with dagger 2 (within activity scope) - android

My question is similar to this.
So for instance, I have a LiveData implementation:
public class CustomLiveData extends LiveData<SomeEvent> {
#Inject
public CustomLiveData(#ActivityContext Context context) {
//....
}
}
that I want to inject into a custom view:
public class CustomView extends View {
#Inject
SomeApplicationProvider anyProvider;
#Inject
CustomLiveData dataProvider;
// Getting #com.di.qualifiers.ActivityContext android.content.Context cannot be provided without an #Provides-annotated method.
// #com.di.qualifiers.ActivityContext android.content.Context is injected at com.repositories.CustomLiveData.<init>(context)
// com.repositories.CustomLiveData is injected at com.ui.CustomView.dataProvider com.ui.CustomView is injected at
// com.di.ApplicationComponent.inject(view)
public CustomView(Context context) { this(context, null); }
public CustomView(Context AttributeSet attrs) {
super(context, attrs);
// Works ok for application provider
Application.getComponent(context).inject(this);
}
}
And here is the rest of DI classes:
#ApplicationScope
#Component(
modules = {AndroidInjectionModule.class,
ActivityBuilder.class
})
public interface ApplicationComponent extends AndroidInjector<MyApp> {
void inject(MyApp application);
void inject(CustomView view);
#Component.Builder
abstract class Builder extends AndroidInjector.Builder<MyApp> {
public abstract ApplicationComponent build();
}
}
#ActivityScope
#Module (subcomponents = MainActivitySubcomponent.class)
public abstract class ActivityBuilder {
#Binds
#IntoMap
#ActivityKey(MainActivity.class)
abstract AndroidInjector.Factory<? extends Activity>
bindActivityInjectorFactory(MainActivitySubcomponent.Builder builder);
//...
}
#Subcomponent(modules = {MainActivityModule.class})
public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> {
#Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<MainActivity> {
}
}
#ActivityScope
#Module
public class MainActivityModule {
#Provides
#ActivityContext
public Context provideActivityContext(MainActivity activity) {
return activity;
}
// Seems to be wrong or not enough!?
#Provides
public CustomLiveData provideCustomLiveData(#ActivityContext Context context) {
return new CustomLiveData(context);
}
}
#Qualifier
public #interface ActivityContext{
}
Note, that I don't get any compiler complaints if CustomLiveData is injected into MainActivity instead into the view.
Thanks!

tl;dr Don't inject model layer dependencies inside custom View objects
Subclasses of View are not good targets for Dagger 2 injection. View objects are meant to be drawn and not must else, hence the name "view". The constructors for View should make this clear; they are designed for inflating View objects from attributes specified in XML. In other words, a View object should be able to be specified in a layout.xml file, inflated at the appropriate point in the lifecycle, and then obtained using findViewById(int id), Butterknife or data binding. In this way, the best custom View objects take no dependencies.
If you want to link a View and some data from the model layer, the standard pattern is to write an Adapter like those for RecyclerView and ListView. If this is not possible, using a setter (e.g., setData()) is preferable to passing dependencies from the model layer in the constructor or requesting injection from within one of the lifecycle methods of the View.
If instead you inject your LiveData object inside an Activity or Fragment using the AndroidInjector class the correct Context will be provided without you having to do anything. This explains your comment "I don't get any compiler complaints if CustomLiveData is injected into MainActivity instead into the view."
Once you have injected the LiveData object into the Activity, use one of the above methods (an adapter or a setter) to associate the data with your custom View. See the Google Android Architecture example here where elements from the model layer are injected using Dagger 2 and then associated with a ListView using findViewById and setAdapter()
Link to the Dagger 2 issue where injection of View objects is discussed:
https://github.com/google/dagger/issues/720

Your Dagger hierarchy looks like this:
appcomponent -> activitycomponent
You try to inject activity context inside view, that depends on appcomponent directly.
It's not possible since there is no method that could provide activity context in appcomponent. Instead, inside view, you should retrieve activity (for example using getContext), extract activitycomponent from it and only then inject CustomLiveData.

Related

Dagger 2 Injecting Fragment into Activity - error: [Dagger/MissingBinding]

I really need help with solving the following error:
error: [Dagger/MissingBinding] prj.view.fragment.FragmentA cannot be provided without an #Inject constructor or an #Provides-annotated method. This type supports members injection but cannot be implicitly provided.
A binding with matching key exists in component: prj.di.module.FragmentsModule_ContributeFragmentA.FragmentASubcomponent
prj.view.fragment.FragmentA is injected at
prj.view.activity.MainActivity.fragmentA
prj.view.activity.MainActivity is injected at
dagger.android.AndroidInjector.inject(T) [prj.di.component.ApplicationComponent → prj.di.module.ActivitiesModule_ContributeMainActivity.MainActivitySubcomponent]
I tried this approach but for some reason, this does not work for me, and here are my classes:
#ApplicationScope
#Component(modules = {ApplicationContextModule.class, RetrofitModule.class, ActivitiesModule.class, AndroidSupportInjectionModule.class})
public interface ApplicationComponent extends AndroidInjector<MyApplication> {
#Component.Factory
interface Factory extends AndroidInjector.Factory<MyApplication> {}
}
#Module
public abstract class ApplicationContextModule {
#Binds
#ApplicationScope
#ApplicationContext
abstract Context bindsContext(MyApplication context);
}
#Module
public abstract class ActivitiesModule {
#ActivityScope
#ContributesAndroidInjector(modules = {MainActivityContextModule.class, FragmentsModule.class})
abstract MainActivity contributeMainActivity();
}
#Module
public class MainActivityContextModule {
#Provides
#ActivityScope
#ActivityContext
Context provideContext(MainActivity context){
return context;
}
}
#Module
public abstract class FragmentsModule {
#FragmentScope
#ContributesAndroidInjector()
abstract FragmentA contributeFragmentA();
}
// BaseActivity extends DaggerAppCompatActivity
public class MainActivity extends BaseActivity {
#Inject FragmentA fragmentA;
#Inject
#ApplicationContext
public Context applicationContext;
#Inject
#ActivityContext
public Context activityContext;
...
public class FragmentA extends DaggerFragment {
#Inject
DispatchingAndroidInjector<Fragment> childFragmentInjector;
#Inject
#ActivityContext
Context activityContext;
...
So as you can see I implemented a similar logic as proposed in the link provided above, but for some reason that is not working. Please, help me to understand what's wrong...
I added an empty constructor annotated with #Inject for the Fragment and it worked... Don't completely understand why Activity injection works without it and Fragment injection not but the problem is solved
If you have ApplicationScope > ActivityScope > FragmentScope then your Activity (ActivityScope) can't directly inject a Fragment (FragmentScope), because the Fragment isn't part of the Activity's graph, the same way you can't inject that Activity in your Application. Subcomponents can inject objects available on the parent component, but not the other way around.
The simplest solution would be to not inject the Fragment and just create it like you usually would. You can inject the Activity into the Fragment (parent > child scope) and use any other Activity / Fragment scoped objects there without further problems.

Why Dagger2 inject the same object but with 2 different instances?

ArticlesContract.Presenter is a new instance in Adapter, which is different with ArticleListFragment, so my data was lost ! I have no idea why I got two different instances:
#Module
public class ArticleListFragmentModule {
#Provides
ArticlesContract.Presenter provideArticlesPresenter(ArticlesPresenter presenter) {
return presenter;
}
}
public class ArticleListFragment extends DaggerFragment implements ArticlesContract.View {
#Inject
ArticlesContract.Presenter mPresenter; //one instance
}
public class ArticlesAdapter extends RecyclerView.Adapter<ArticleViewHolder> {
#Inject
ArticlesContract.Presenter mPresenter; //another different instance
}
2018-05-16 UPDATED: this issues is fixed by following #Fred answer : lack a scope and managing this scope:
#Scope
#Documented
#Retention(RetentionPolicy.RUNTIME)
public #interface ArticlesScope {
}
#Module
public abstract class ActivityBuilder {
#ContributesAndroidInjector(modules = ArticleListFragmentModule.class)
#ArticleListScope
abstract ArticleListFragment bindArticleListFragment();
}
#Module
public class ArticleListFragmentModule {
/**
* Provide dependency for interface.
* Interface cannot be annotated with #Inject, otherwise it will cause, error: ArticlesContract.Presenter cannot be provided without an #Provides- or #Produces-annotated method.
*/
#Provides
#ArticleListScope
ArticlesContract.Presenter provideArticlesPresenter(ArticlesPresenter presenter) {
return presenter;
}
}
Scoping with #ContributesAndroidInjector, refer to Dagger 2 Annotations: #Binds & #ContributesAndroidInjector
This is because you lack a scope and managing this scope. You need to create a scope yourself or use one provided by dagger already. Here since it's a presenter it can be the Reusable scope I guess or Singleton. However, Singleton has a performance impact that might not be desirable.
The next important thing is that you need to understand that while the component's instance is the same the provided binding will be the same (excluding the case of the Reusable scope). In other words, scopes provide a way of telling dagger - "while this component is alive and it's scope is X then all instances scoped with X will be the same". Here's what I mean in code:
#Scope
#Documented
#Retention(RUNTIME)
public #interface PresenterScope {}
#Module
public class ArticleListFragmentModule {
#Provides
#PresenterScope
ArticlesContract.Presenter provideArticlesPresenter(ArticlesPresenter presenter) {
return presenter;
}
}
I don't know how you've set up your component, but you'd have to annotate it also with the PresenterScope. Now, it's just a matter of making sure that when you inject ArticleListFragment and ArticlesAdapter you'll be using the same instance of the component. If you rebuild the component than the instance of the presenter will be different.
Remember that Reusable is a bit different, but it should suit your needs here since the presenter should hold no state.
Hope this helps

Dagger : Why does dagger require a #inject constructor for an object that does't depend on another object

I think I'm missing something. I get this error:
PostsVM cannot be provided without an #Inject constructor or from an
#Provides-annotated method.
Assume classes as follows :
#Module
public class AppModule {
private final Application mApplication;
#Singleton
#Provides
ViewModel provideListViewModel() {
return new PostsVM();
}
}
And a class PostVM
#Singleton
public class PostsVM extends ViewModel {
public boolean get(){
return true;
}
}
And a component :
#Singleton
#Component(modules = AppModule.class)
public interface AppComponent {
void inject(Global global);
void inject(MainActivity mainActivity);
#Architecture.ApplicationContext
Context getContext();
Application getApplication();
}
And in activity :
#Inject
public ViewModelProvider.Factory factory;
#Override
protected void onCreate(Bundle savedInstanceState) {
InjectorClass.inject(this);
As you can see, the example given for PostVM class, does't depend on anything, why do I need an #inject constructor in it?
tl;dr To prevent errors and to follow convention.
From the JavaDoc of #Inject you can read:
Injectable constructors are annotated with #Inject and accept zero or more dependencies as arguments. #Inject can apply to at most one constructor per class.
And it's always good practice to follow the convention / documentation.
So #Inject marks an entry point for Dagger to signal it how and where to create your class. It is a clear sign of how you intend your class to be used.
What if you have multiple constructors?
What if you require additional setup and should use a #Module instead?
By just defaulting to a no-args constructor (if possible) things could start breaking very easily and you might not be able to pinpoint the source easily if you just assume Dagger does its job.
__ cannot be provided without an #Inject constructor or from an #Provides-annotated method.
This error on the other hand gives you a strong signal that you're missing something and cannot be ignored.

How to declare dependencies

I'm studying Dagger 2 so I would like to understand some basic things. I have the following code:
#Module
public class MainModule {
#Provides
public Presenter provideMainActivityPresenter(Model model){
return new MainPresenter(model);
}
#Provides
public Model provideMainModel(){
return new MainModel();
}
}
and my MainPresenter class looks like this:
public class MainPresenter implements Presenter {
#Nullable
private ViewImpl view;
private Model model;
public MainPresenter(Model model) {
this.model = model;
}
#Override
public void setView(ViewImpl view) {
this.view = view;
}
}
Instead of the above code, could I do the following?
public class MainPresenter implements Presenter {
#Nullable
private ViewImpl view;
#Inject
Model model;
#Override
public void setView(ViewImpl view) {
this.view = view;
}
}
Because the MainPresenter depends on the Model and it is not #Nullable.
Or this is wrong?
I don't understand when I should put a dependency as a constructor argument, or when I should use #Inject
You have basically 3 ways to use Dagger
Constructor injection
Field injection
Providing it from a module yourself
(There is also method injection which calls a method after creating your object)
The following is using a module that provides your class. While not wrong, this is the most overhead to write and maintain. You create the object by passing in the requested dependencies and return it:
// in a module
#Provides
public Presenter provideMainActivityPresenter(Model model){
// you request model and pass it to the constructor yourself
return new MainPresenter(model);
}
This should be used with things that require additional setup, like Gson, OkHttp, or Retrofit so that you can create the object in one place with the required dependencies.
The following would be used to inject objects where you don't have access or don't want to use the constructo. You annotate the field and register a method at the component to inject your object:
#Component class SomeComponent {
void injectPresenter(MainPresenter presenter);
}
public class MainPresenter implements Presenter {
// it's not annotated by #Inject, so it will be ignored
#Nullable
private ViewImpl view;
// will be field injected by calling Component.injectPresenter(presenter)
#Inject
Model model;
// other methods, etc
}
This will also provide you with overhead to register all your classes at a presenter and should be used when you can't use the constructor, like Activities, Fragments, or with Services. That's why all those Dagger samples have those onCreate() { DaggerComponent.inject(this); } methods to inject parts of the Android framework.
Most importantly you can use Constructor Injection. You annotate the constructor with #Inject and let Dagger find out how to create it.
public class MainPresenter implements Presenter {
// not assigned by constructor
#Nullable
private ViewImpl view;
// assigned in the constructor which gets called by dagger and the dependency is passed in
private Model model;
// dagger will call the constructor and pass in the Model
#Inject
public MainPresenter(Model model) {
this.model = model;
}
}
This only requires you to annotated your class constructor and Dagger will know how to handle it, given that all the dependencies (constructor arguments, Model in this example) can be provided.
All of the methods mentioned above will create an object and can / should be used in different circumstances.
All of those methods either pass the dependencies to the constructor, or inject #Inject annotated fields directly. Dependencies should thuse be in the constructor or annotated by #Inject so that Dagger knows about them.
I also wrote a blog post about the basic usage of Dagger 2 with some further details.

How to inject a Presenter into a View (MVP pattern) using Dagger2

I want to build an Android app using the MVP pattern.
I have a fragment (the view) and a presenter class.
What I want is to basically inject the presenter into the fragment, and set the fragment as the presenter's view (via an interface that the view will implement)
How can I easily and correctly connect the 2 using dependency injection (with Dagger2)?
Edit:
In addition, I'd like the presenter to be a singleton, so it will be able to persist data & state across orientation changes
First you need to define a presenter module:
#Module
class SearchPresenterModule {
#NonNull
private final SearchContract.View mView;
SearchPresenterModule(#NonNull SearchContract.View view) {
this.mView = view;
}
#Provides
SearchContract.View provideSearchContractView() {
return mView;
}
}
Here's the example component:
#FragmentScoped
#Component(modules = SearchPresenterModule.class)
interface SearchComponent {
void inject(SearchActivity activity);
}
And inject your presenter:
#Inject
SearchPresenter mSearchPresenter;
DaggerSearchComponent.builder()
.searchPresenterModule(new SearchPresenterModule(searchFragment))
.build()
.inject(this);
Finally inject your presenter's constructor:
#Inject
SearchPresenter(#NonNull SearchContract.View view, #NonNull SearchRepository searchRepository) {
this.mView = view;
mView.setPresenter(this);
}
Extra: Here's fragmentscoped annotation:
#Documented
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface FragmentScoped {
}
You can check my example repo for MVP + DAGGER2
https://github.com/savepopulation/wikilight
so the presenter is like
#Singleton
public class Presenter{
private View mView; ...
the view should be
public class View extends ...{
#Inject
protected Presenter mPresenter ...
Well, you just need a method in your module like
inject(View view)
and Dagger should take care of the rest like the singleton instance and the injection

Categories

Resources