web-dev-qa-db-fra.com

Puis-je simplement injecter une super classe lorsque j'utilise dagger2 pour l'injection de dépendances?

J'utilise Dagger2 pour DI dans mon Android. J'ai constaté que je devais écrire la méthode d'injection pour chaque classe qui utilise le champ @Inject. Existe-t-il un moyen de simplement injecter la classe parent afin que je n'ai pas à appeler inject sur chaque sous-classe? Prenez l'activité par exemple. J'ai un BaseActivity à partir duquel chaque activité s'étend. Existe-t-il un moyen de créer simplement une méthode inject dans le composant pour BaseActivity et appelez juste inject dans BaseActivity onCreate, et les champs @inject dans les sous-activités sont injectés automatiquement?

43
Chris.Zou

Cela ne peut pas être fait pour le moment. Explication de Gregory Kick (de ici ):

Voici comment fonctionnent les méthodes d'injection des membres:

  1. Vous pouvez créer une méthode d'injection de membres pour tout type contenant @Inject N'importe où dans sa hiérarchie de classes. Si ce n'est pas le cas, vous obtiendrez une erreur.
  2. Tous les membres @Inject Ed dans la hiérarchie de types entière seront injectés: le type d'argument et tous les supertypes.
  3. Aucun membre ne sera @Inject Éd pour les sous-types du type d'argument.

Ce problème a été discuté ici et ici , suivez-les pour les mises à jour. Mais il est peu probable qu'il change bientôt, car Dagger 2 est proche de la sortie .

29
Kirill Boyarshinov

J'ai rencontré la même situation. Une façon de faciliter un peu l'injection à partir d'un composant commun à toutes les activités est la suivante:

1) Étendez la classe Application pour pouvoir créer le composant commun et y conserver une référence.

public class ApplicationDagger extends Application {

    private ApplicationComponent component;

    @Override
    public void onCreate(){
        super.onCreate();
        component = DaggerApplicationComponent.builder().applicationModule(new ApplicationModule(this)).build();
    }

    public ApplicationComponent getComponent(){
            return component;
    }
}

2) Créez un DaggerActivity abstrait qui obtient le composant commun d'Application et appelle une méthode abstraite injectActivity, donnant le composant comme argument. Comme ça:

public abstract class DaggerActivity extends Activity {

    @Override
    public void onCreate(Bundle saved){
        super.onCreate(saved);
        ApplicationComponent component = ((ApplicationDagger) getApplication()).getComponent();
        injectActivity(component);
    }

    public abstract void injectActivity(ApplicationComponent component);
}

3) Enfin, vous devez réellement injecter chaque Activity extension DaggerActivity. Mais cela peut être fait avec moins d'efforts maintenant, car vous devez implémenter la méthode abstract sinon vous obtiendrez des erreurs de compilation. Et c'est parti:

public class FirstActivity extends DaggerActivity {

    @Inject
    ClassToInject object;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //initialize your Activity
    }

    @Override
    public void injectActivity(ApplicationComponent component) {
        component.inject(this);
    }
}

Bien sûr, vous devez toujours déclarer chaque activité explicitement dans votre composant.

MISE À JOUR: Injection d'objets @ActivityScope dans des fragments

À un moment donné, j'ai dû utiliser étendues personnalisées pour lier des objets à un cycle de vie Activity. J'ai décidé d'étendre ce poste car cela pourrait aider certaines personnes.

Supposons que vous ayez une classe @ Module ActivityModule et une @ Subcomponent interface ActivityComponent.

Vous devrez modifier le DaggerActivity. L'extension ActivitiesDaggerActivity devrait implémenter la nouvelle méthode (changement de signature).

public abstract class ActivityDagger extends AppCompatActivity {

    ActivityComponent component;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        component = ((ApplicationDagger) getApplication()).getComponent().plus(new ActivityModule(this));
        injectActivity(component);
        super.onCreate(savedInstanceState);
    }

    ActivityComponent getComponent() {
        return component;
    }

    public abstract void injectActivity(ActivityComponent component);
}

Ensuite, une classe FragmentDagger étendant Fragment peut être créée comme ceci:

public abstract class FragmentDagger extends Fragment {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityDagger activityDagger = (ActivityDagger) getActivity();
        ActivityComponent component = activityDagger.getComponent();
        injectFragment(component);
    }

    public abstract void injectFragment(ActivityComponent component);

}

Quant au Activities, le Fragments étendant FragmentDagger n'a qu'une seule méthode à implémenter:

public abstract void injectFragment(ActivityComponent component);

Vous devriez pouvoir réutiliser Fragments où vous voulez. Notez que la méthode super.onCreated() dans ActivityDagger doit être appelée après l'instanciation du composant. Sinon, vous obtiendrez NullPointerException lorsque l'état Activity sera recréé, car la méthode super.onCreate() de Fragment sera appelée.

41
Gordak

Vous pouvez faire un petit hack en utilisant la réflexion:

public class UiInjector {

    private static final String METHOD_NAME = "inject";

    private final UIComponent component;

    public UiInjector(final UIComponent component) {
        this.component = component;
    }

    public void inject(final Object subject) {
        try {
            component.getClass()
                    .getMethod(METHOD_NAME, subject.getClass())
                    .invoke(component, subject);
        } catch (final NoSuchMethodException exception) {
            throwNoInjectMethodForType(component, subject.getClass());
        } catch (final Exception exception) {
            throwUnknownInjectionError(exception);
        }
    }

    private void throwNoInjectMethodForType(final Object component, final Class subjectType) {
        throw new RuntimeException(component.getClass().getSimpleName() +
                " doesn't have inject method with parameter type : " + subjectType);
    }

    private void throwUnknownInjectionError(final Exception cause) {
        throw new RuntimeException("Unknown injection error", cause);
    }
}

Dans ce cas, vous devez toujours écrire la méthode inject dans un composant, mais vous n'avez pas besoin de la méthode 'inject' dans chaque activité, fragment, vue, etc.

Pourquoi ça marche? lorsque nous utilisons getClass() sur injection, le sujet obtient une classe descendante, pas une base.

Mise en garde! Dans le cas où vous utilisez Proguard, vous devez ajouter le prochain -keep class <ComponentClass> { *; } À vos règles afin de conserver les méthodes d'injection telles quelles dans le composant

2