Android
Module und Provider in Roboguice
Posted on .Dependency Injection Frameworks wie Roboguice trennen die Erzeugung von Objektgraphen vom Rest der Geschäftslogik. Oft reichen die Default-Einstellungen aus, damit Roboguice weiß, welche Klassen es verwenden soll und wie es Instanzen konstruiert.
Roboguice bietet die Möglichkeit, die Konstruktion von Objekten zu konfigurieren und verschiedene sogenannte Module anzulegen, die unterschiedliche Implementierungen von Objekt-Graphen bündeln.
Module
Roboguice injiziert standardmäßig eine Instanz vom Typ des zu injizierenden Attributs. Was passiert aber, wenn z.B. der Typ eines Attributes eine Schnittstelle ist? Module verbinden die (abstrakten) Typen, die mit @Inject
für die Injektion markiert wurden mit konkreten Implementierungen. Das kann die Verbindung einer abstrakten Klasse oder einer Schnittstelle mit einer konkreten Klasse sein, die die Implementierung erfüllt oder das Ersetzen einer Klasse durch eine andere, die von ihr erbt.
public class BuildModule extends AbstractModule {
private Application mAppContext;
public BuildModule(Application appContext) {
mAppContext = appContext;
}
@Override
protected void configure() {
// bindet eine Schnittstelle an eine konkrete Implementierung
this.bind(ICombustible.class).to(Gasoline.class);
// bindet eine Subklasse an eine Basisklasse
this.bind(Engine.class).to(PetrolEngine.class);
}
}
Damit Roboguice von der Existenz eines Moduls erfährt, muss es registriert werden. Das kann auf zwei Wegen geschehen. Über einen Tag in der AndroidManifest.xml
Datei, der den vollständigen Namen der Modul-Klasse enthält, bzw. eine Liste der Modulnamen, die durch Kommata getrennt sind
<application ...>
<meta-data android:name="roboguice.modules"
android:value="de.infinitestatemachine.BuildModule" />
</application>
Der andere Weg ist programatisch durch die Methode getOrCreateBaseApplicationInjector()
, die eine beliebige Anzahl an Modul-Instanzen als Argument akzeptiert. Unter den Modulen muss auch das DefaultRoboModule
sein, sonst können keine Klassen aus dem Android SDK injiziert werden.
RoboGuice.getOrCreateBaseApplicationInjector(application, RoboGuice.DEFAULT_STAGE, RoboGuice.newDefaultRoboModule(application), new BuildModule(application));
Für den Einsatz bei UnitTests existiert noch die Methode overrideApplicationInjector()
, die automatsich das DefaultRoboModule und alle Module aus der AndroidManifest.xml
Datei lädt sowie zusätzlich als Argument übergebene Module speziell für die Testumgebung.
RoboGuice.overrideApplicationInjector(Robolectric.application, new TestModule());
Provider
Provider sind quasi Fabriken, die Roboguice benutzen kann, um Objekte zu konstruieren, die es sonst nicht automatisch erzeugen könnte. Das kann z.B. nötig sein wenn Objekte aus einer Bibliothek verwendet werden, die erst manuell initialisiert werden müssen, weil Roboguice dessen Abhängigkeiten nicht kennen kann ohne die entsprechenden Annotationen zur Kennzeichnung.
Ein Provider implementiert die Schnittstelle Provider und hat eine Methode get()
, die eine neue Instanz des gewünschten Typs liefert
public class CombustibleProvider implements Provider {
@Override
public ICombustible get() {
return new Gasoline();
}
}
Dann muss nur noch im Modul der entsprechende Typ an den Provider gebunden werden:
public class BuildModule extends AbstractModule {
@Override
protected void configure() {
// bindet eine Schnittstelle an einen Provider
this.bind(ICombustible.class)
.toProvider(CombustibleProvider);
}
}
Anstatt für jede Schnittstelle eine Provider-Klasse zu schreiben und sie dann im Modul noch mal an die Schnittstelle zu binden, bietet Roboguice mit der Annotation @Provides
eine Abkürzung. Man mit ihr eine öffentliche Methode in einem Modul markieren, die eine neue Instanz erzeugt. Der Rückgabe-Typ der Methode entspricht dem Typ, an den diese Provider-Methode gebunden wird.
public class BuildModule extends AbstractModule {
// Diese Methode bindet den Typ „ICombustible“
// an die Implementierung „Gaosline“
@Provides
public ICombustible getCombustible(){
return new Gasoline();
}
}
Named Injections
Manchmal kann es nötig sein, verschieden konfigurierte Instanzen des selben Typs injizieren zu können. Man braucht also eine Möglichkeit, einem einzelnen Typ mehre Provider zuzuordnen. Dazu müssen die entsprechenden Attribute, die eine Injektion erwarten zusätzlich mit der Annotation @Named()
versehen werden, der ein String zur Unterscheidung übergeben wird. Mit den selben Annotationen werden dann in einem Modul die Provider-Methoden versehen, die die unterschiedlichen Instanzen des Typs produzieren.
@Inject
@Named("blue_team")
private Player player;
public class GameModule extends AbstractModule {
@Provides
@Named("blue_team")
public Player getBluePlayer(){
return new Player(new BlueSkin());
}
@Provides
@Named("red_team")
public Player getRedPlayer(){
return new Player(new RedSkin());
}
}
Statt mit Annotationen lassen sich Bindings mit Namen auch programmatisch ausdrücken durch die Methode annotatedWith()
, die eine Annotation mit dem entsprechenden Namen entgegen nimmt. Ein solches Annotations-Objekt kann mit der Methode Names.named()
erzeugt werden.
public class GameModule extends AbstractModule {
@Override
protected void configure() {
this.bind(Player.class).annotatedWith(Names.named("blue_team"))
.toProvider(BluePlayerProvider.class);
this.bind(Player.class).annotatedWith(Names.named("red_team"))
.toProvider(RedPlayerProvider.class);
}
}