web-dev-qa-db-fra.com

Injection de dépendance CustomView avec poignard 2 (dans le cadre de l'activité)

Ma question est similaire à this .

Ainsi, par exemple, j'ai une implémentation LiveData:

public class CustomLiveData extends LiveData<SomeEvent> {

    @Inject
    public CustomLiveData(@ActivityContext Context context) {
        //....
    }

}

que je souhaite injecter dans une vue personnalisée:

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);
   }
}

Et voici le reste des classes DI:

@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{
}

Notez que je ne reçois aucune plainte du compilateur si CustomLiveData est injecté dans MainActivity à la place dans la vue. Merci!

16
sinek

tl; dr N'injectez pas de dépendances de couche de modèle dans des objets View personnalisés

Les sous-classes de View ne sont pas de bonnes cibles pour l'injection de Dagger 2. Les objets View sont destinés à être dessinés et ne doivent pas l'être autrement, d'où le nom "view". Les constructeurs de View devraient clarifier cela; ils sont conçus pour gonfler les objets View à partir des attributs spécifiés en XML. En d'autres termes, un objet View doit pouvoir être spécifié dans un fichier layout.xml, Gonflé au point approprié du cycle de vie, puis obtenu à l'aide de findViewById(int id), - Butterknife ou liaison de données. De cette façon, les meilleurs objets View personnalisés ne prennent aucune dépendance.

Si vous souhaitez lier un View et certaines données de la couche modèle, le modèle standard consiste à écrire un adaptateur comme ceux pour RecyclerView et ListView. Si cela n'est pas possible, l'utilisation d'un setter (par exemple, setData()) est préférable à la transmission des dépendances de la couche modèle dans le constructeur ou à la demande d'injection à partir de l'une des méthodes de cycle de vie du View .

Si, à la place, vous injectez votre objet LiveData dans une activité ou un fragment en utilisant la classe AndroidInjector, le bon Context sera fourni sans que vous ayez à faire quoi que ce soit. Cela explique votre commentaire "Je ne reçois aucune plainte du compilateur si CustomLiveData est injecté dans MainActivity à la place dans la vue."

Une fois que vous avez injecté l'objet LiveData dans l'activité, utilisez l'une des méthodes ci-dessus (un adaptateur ou un setter) pour associer les données à votre View personnalisé. Voir l'exemple Google Android Architecture ici où les éléments de la couche modèle sont injectés à l'aide de Dagger 2 puis associés à un ListView à l'aide de findViewById et setAdapter()

Lien vers le problème de Dagger 2 où l'injection d'objets View est discutée:

https://github.com/google/dagger/issues/72

36
David Rawson

Votre hiérarchie Dagger ressemble à ceci: appcomponent -> activitycomponent

Vous essayez d'injecter l'activité context dans la vue intérieure, qui dépend directement de appcomponent.

Ce n'est pas possible car il n'y a aucune méthode qui pourrait fournir un contexte d'activité dans appcomponent. Au lieu de cela, dans la vue intérieure, vous devez récupérer l'activité (par exemple en utilisant getContext), en extraire activitycomponent et ensuite seulement injecter CustomLiveData.

1
dominik4142