2015-03-28 3 views
27

Ich benutze Dagger2 für DI in meiner Android-Anwendung. Ich fand, dass ich inject-Methode für jede Klasse schreiben muss, die @Inject-Feld verwendet. Gibt es eine Möglichkeit, dass ich einfach die Elternklasse injizieren kann, damit ich nicht auf jede Unterklasse injizieren muss? Nehmen Sie zum Beispiel Aktivität. Ich habe eine BaseActivity, dass jede Aktivität erstreckt sich von. Gibt es eine Möglichkeit, dass ich einfach eine inject-Methode in der Komponente für BaseActivity erstellen kann und einfach inject in BaseActivity onCreate aufruft, und @inject-Felder in Unteraktivitäten automatisch injiziert werden?Kann ich nur Super-Klasse injizieren, wenn Sie dagger2 für die Dependency-Injektion verwenden?

+0

Könnten Sie einen Beispielcode hinzufügen, um zu zeigen, was Sie meinen? – nhaarman

Antwort

21

Es kann nicht sofort gemacht werden. Erklärung von Gregory Stoss-Schützen:

Hier ist, wie Mitglieder Injektionsverfahren arbeiten:

  1. Sie können ein Mitglieder-Injektionsverfahren für jede Art machen, die @Inject überall in seiner Klassenhierarchie hat. Wenn nicht, erhalten Sie einen Fehler.
  2. Alle @Inject ed-Member in der gesamten Typhierarchie werden injiziert: der Argumenttyp und alle Supertypen.
  3. Keine Mitglieder werden @Inject Ed für Subtypes des Argumenttyps.

Dieses Problem wurde here und here, diskutiert diese Updates folgen zu lassen. Aber es ist unwahrscheinlich, dass sich das bald ändert, denn Dagger 2 ist close to release.

+0

Es scheint, dass sie diese Entscheidung aus einem bestimmten Grund getroffen haben. Aber es ist immer noch schade, dass sie das nicht unterstützen, da es ein wenig kontraintuitiv ist. Trotzdem, danke für die Antwort! –

+0

@ Chris.Zou Um die Subklasseninjektion zu unterstützen, müssten Sie zur Laufzeit Reflektion durchführen. Das Dagger 2-Team hat schon früh entschieden, dass sie es vermeiden wollen, Dinge zur Laufzeit zu tun, da es langsamer ist und man erst dann Fehler erfährt, wenn man die App startet. – vaughandroid

35

Ich stieß auf die gleiche Situation. Eine Möglichkeit, die Injektion aus einer gemeinsamen Komponente in allen Aktivitäten etwas zu erleichtern, ist die folgende:

1) Erweitern Sie die Application-Klasse, um die gemeinsame Komponente erstellen zu können und einen Verweis darauf zu behalten.

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) Erstellen Sie eine abstrakte DaggerActivity, die die gemeinsame Komponente von Anwendungs ​​bekommt und ruft eine abstrakte Methode injectActivity, die Komponente als Argument zu geben. Wie folgt aus:

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) Last, müssen Sie injizieren tatsächlich jede ActivityDaggerActivity erstreckt. Dies kann jedoch jetzt mit weniger Aufwand erfolgen, da Sie die Methode abstract implementieren müssen, da sonst Kompilierungsfehler auftreten. Hier gehen wir:

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

Natürlich müssen Sie noch jede Aktivität explizit in Ihrer Komponente deklarieren.

UPDATE: Injecting @ActivityScope in Fragmente Objekte

, Irgendwann musste ich custom scopes verwenden, um Objekte zu binden, zu einem Activity Lebenszyklus. Ich habe beschlossen, diesen Beitrag auszuweiten, da es einigen Leuten helfen könnte.

Angenommen, Sie haben eine ActivityComponent@Module Klasse ActivityModule und eine @Subcomponent Schnittstelle.

Sie müssten die DaggerActivity ändern. Die Activities Erweiterung DaggerActivity müsste die neue Methode (Signaturwechsel) implementieren.

public abstract class ActivityDagger extends AppCompatActivity { 

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

Dann wird eine Klasse FragmentDaggerFragment erstrecken kann wie folgt erstellt werden:

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

} 

Was die Activities, die FragmentsFragmentDagger erstreckt haben nur eine Methode zu implementieren:

public abstract void injectFragment(ActivityComponent component); 

Sie sollten in der Lage sein, Fragments wo immer Sie wollen wiederzuverwenden. Beachten Sie, dass die Methode super.onCreated() in ActivityDagger nach der Komponenteninstanziierung aufgerufen werden sollte. Andernfalls erhalten Sie NullPointerException, wenn der Activity-Status neu erstellt wird, da die Methode super.onCreate() des Fragment aufgerufen wird.

+0

Danke für den Tipp! –

2

Sie können ein wenig Hack tun Reflexion mit:

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

In diesem Fall müssen Sie noch Methode in einer Komponente injizieren schreiben, aber Sie brauchen nicht 'Inject' Methode in jeder Aktivität, Fragment, Ansicht, was auch immer.

Warum es funktioniert? wenn wir getClass() auf der Einspritzung verwenden, erhält Subjekt eine Nachkommenklasse, nicht Basis.

Vorsicht! Wenn Sie Proguard verwenden, müssen Sie als nächstes -keep class <ComponentClass> { *; } zu Ihren Regeln hinzufügen, um die Injekt-Methoden so zu behalten, wie in Komponente

Verwandte Themen