Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CustomView dependency injection with dagger 2 (within activity scope)

Tags:

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!

like image 521
sinek Avatar asked Jun 30 '17 10:06

sinek


2 Answers

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

like image 166
David Rawson Avatar answered Sep 22 '22 16:09

David Rawson


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.

like image 32
dominik4142 Avatar answered Sep 19 '22 16:09

dominik4142