-
Notifications
You must be signed in to change notification settings - Fork 0
Home
Dependency Injeciton oder kurz DI bezeichnet den Vorgang Abhängigkeiten in Klassen einzuspeisen. Als Beispiel lässt sich der folgende Fall anführen:
Für die Durchführung einer Aufgabe benötigt die Klasse Manager
die Methode doImportantWork()
der Klasse SupportClass
. Weil diese Methode allerdings einen internen Zähler besitzt, welcher die Aufrufe der Methode loggt, kann diese Methode nicht als statisch definiert werden. Denkbar ist auch jede andere Form von internen Variablen wie Pfade, URL's oder ähnlichem.
Es wäre jetzt möglich die Abhängigkeiten manuell zu verwalten. Dies kann jedoch schnell sehr aufwendig und unübersichtlich werden. Ein gutes Negativbeispiel ist in einem anderen Repository von mir enthalten (siehe SettingsFragment.java). Dort wird die benötigte Variable
(42) PreferencesManager preferencesManager
manuell über die MainActivity abgerufen
(56) preferencesManager = activity.pManager
.
Einfacher ist es, die Abhängigkeiten mittels Dagger2 durchzuführen.
Mithilfe der Informationen, welche der Programmierer Dagger zur Verfügung stellen muss, generiert es Code. Dieser übernimmt komplett das Einsetzen von Abhängigkeiten (also bspw. Klasseninstanzen) in eine andere Klasse.
Als Programmierer muss ich mir dann keine Gedanken mehr machen, wie ich bspw. eine Instanz von SupportClass
in meine Manager
Klasse einbringen kann.
Dagger braucht grundsätzlich nur eine Klasse (das Module) und ein Interface (die/das? Component) sowie die Annotation @Inject
in den Klassen für welche eine DI durchgeführt werden soll.
Dieses Interface sagt Dagger in welchen Klassen nach den @Inject Annotations gesucht werden soll.
Im Beispiel wird dies durch die ComponentDI
realisiert (siehe ComponentDI.java).
/**
* In diesem Interface wird für den Dagger Pre-Prozessor definiert für welche Klassen er Methoden für die DI bereistellen soll.
*/
@Component(modules = {ModuleDI.class})
public interface ComponentDI {
//Bedeutung: Implementiere eine Methode, die die in der Klasse ModuleDI bereitgestellten Elemente in die Klasse MainActivity injecten kann.
void inject(MainActivity mainActivity);
// Wir möchten auch etwas in ein Fragment injecten können
void inject(SecondFragment secondFragment);
// Und in einem anderen Fragment soll das auch möglich sein
void inject(FirstFragment firstFragment);
// Und wir werden wahrscheinlich in der Applicationklasse eine Injection brauchen
void inject(ApplicationDI applicationDi);
}
Jetzt kann Dagger nach Codezeilen suchen in denen eine Injection vorgenommen werden muss. Um diese durchzuführen braucht Dagger allerdings eine Instanz des angefragten Objektes. Bis jetzt weiß Dagger aber noch nicht wirklich von wo es diese beziehen kann.
Um Dagger zu erklären woher die Objekte bezogen werden, wird die folgende Zeile benötigt:
@Component(modules = {ModuleDI.class})
Mit dieser Annotation weiß Dagger nun, dass die Objektinstanzen in der Klasse ModuleDI
zu finden sind.
Wie schon erläutert, wird in der Module Klasse defniert wie Dagger die Instanzen der Objekte beziehen soll.
Als erstes wird die Annotation @Module
benötigt um die Klasse als Module Klasse zu markieren.
Im Klassenkonstruktor können Parameter verwendet werden. Es empfiehlt sich hier ein grundlegendes Objekt, wie den Context oder die Application, zu übergeben aus welcher alle weiteren Abhängigkeiten generiert werden können. An dieser Stelle wird über die Konstruktorparameter quasi eine manuelle DI durchgeführt.
Verschiedene Methoden definieren nun wie die einzelnen Objekte erzeugt werden. Sie sind mit der Annotation @Provides
gekennzeichnet. Interessant ist, dass durch Dagger alle Parameter einer solchen Methode automatisch mit den passenden Objekten versorgt wird. Im nachfolgenden Code als bspw. in den Methoden provideSettingsManager
und provideSettingsManager
.
Um zu vermeiden, dass Dagger für jede DI eine eigene Instanz eines Objektes erzeugt, ist es notwendig eine weitere Annotation zu verwenden. Mithilfe von @Singleton
wird Dagger angewiesen nur beim ersten Aufruf eine neue Instanz zu erzeugen und anschließend immer wieder auf diese zurückzugreifen (mehr zum Thema Scopes gibt es hier).
Im Beispiel wird dies durch die ModuleDI
realisiert (siehe ModuleDI.java).
/**
* Diese Klasse definiert die Instanzierung der Variablen
* Hier werden die absolut minimalen Abhängigkeiten hinterlegt. Dies sind im aktuellen Fall, die MainActivity und der Context
* Zwischen den einzelnen @Provide Methoden muss keine Verbindung bzgl. der eingegebenen Variablen hergestellt werden. Dies übernimmt Dagger
*/
@Module
public class ModuleDI {
private final Application application;
private ExampleManager mExampleManager;
public ModuleDI(Application application) {
this.application = application;
}
@Provides
Context provideContext() {
return application.getApplicationContext();
}
/**
* Beispiel für die Variablenverknüpfung
* Dagger verknüpft automatisch die in provideContext bereitgestellte Instanz von Context mit dem benötigten Input für diese Methode.
* @param context Der benötigte Context
* @return Eine Instanz von ExampleManager
*/
@Provides
@Singleton
// Damit wir auf alle Anfragen den gleichen ExampleManager zurückbekommen, wird diese Annotation verwendet.
// Sie definiert, dass in der Applicationscope nur einmal dieses Objekt erzeugt wird.
ExampleManager provideSettingsManager(Context context) {
return new ExampleManager(context);
}
/**
* Weiteres Beispiel für die Variablenverknüpfung
* Dagger verknüpft automatisch die in provideSettingsManager bereitgestellte Instanz von ExampleManager mit dem benötigten Input für diese Methode.
* @param exampleManager ExampleManager von dem die ID geholt werden soll
* @return Die ID des Managers
*/
@Provides
// Als Beispiel wird von der SecondClass jedes mal eine eigenständige Instanz ausgeführt.
SecondClass provideSecondClass(ExampleManager exampleManager) {
return new SecondClass(exampleManager);
}
}
Um das entstehende Dagger Gebilde aber vernünftigt in allen Subklassen (die Zugriff auf getApplication
habe) instanzieren zu können, braucht es noch eine weitere Klasse. Diese erweitert die Application
Klasse von Android mit einer Instanzierung der, von Dagger erstellten, Implementierung auf Basis unserers Component Interface. Zusätzlich wird noch eine Methode benötigt um diese Instanz an alle weiteren Klassen zu verteilen, damit diese für sich eine Injection "beantragen" können.
Im Beispiel wird dies durch die ApplicationDI
realisiert (siehe ApplicationDI.java).
/**
* Eigene Application Klasse welche zusätzliche Methoden für die DI bereitstellt.
* Dies ist notwendig, damit die getComponent Methode in den Activity und Fragment Klassen abgerufen werden kann
*/
public class ApplicationDI extends Application {
// Um überhaupt erst etwas für die Injection zu haben, wird eine Instanz der ComponentDI benötigt. Den dort werden alle Dependencies definiert.
private ComponentDI mComponent;
/**
* Beim Erstellen der Application wird diese Methode aufgerufen und die von Dagger implementierte Component wird initalisiert
*/
@Override
public void onCreate() {
super.onCreate();
mComponent = DaggerComponentDI
.builder()
.moduleDI(new ModuleDI(this))
.build();
}
/**
* Führt die Instanzierung der Component durch
* @return Die Component
*/
public ComponentDI getComponent() {
// Falls noch keine Instanz vorhanden ist (was eigentlich der Fall sein sollte), muss diese erstellt werden
if (mComponent == null) {
// Component instanzieren
mComponent = DaggerComponentDI.builder()
// Das Modul implementieren und mit den Basisabhängigkeiten versorgen (MainActivity)
.moduleDI(new ModuleDI(this))
.build();
}
return mComponent;
}
}
Grundsätzlich ist dies sehr einfach. Als erstes muss Dagger noch einmal explizit angewiesen werden die Injection durchzuführen. Dazu muss die Application abgerufen werden, diese in die angepasste Application Klasse gecastet werden und anschließend die dort definierten Befehle ausgeführt werden. Dies alles lässt sich in einer Zeile zusammenfassen.
Für eine Activity sieht die Zeile so aus:
((ApplicationDI) getApplication()).getComponent().inject(this);
Für ein Fragment so:
((ApplicationDI) getActivity().getApplication()).getComponent().inject(this);
Die Markierung der einzusetzenden Klassen erfolgt dann mittels der @Inject
Annotation.
public class FirstFragment extends Fragment {
IFragmentCallback fragmentCallback;
@Inject
ExampleManager exampleManager;
public FirstFragment() {
// Required empty public constructor
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// DI vornehmen
((ApplicationDI) getActivity().getApplication()).getComponent().inject(this);
// Callback einrichten
try {
fragmentCallback = (IFragmentCallback) getActivity();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_first, container, false);
Button firstButton = view.findViewById(R.id.firstFragmentButton1);
Button secondButton = view.findViewById(R.id.firstFragmentButton2);
firstButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
exampleManager.publishID();
}
});
secondButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
fragmentCallback.switchFragment(1);
}
});
return view;
}
}
Leider gibt es nun eine kleine Einschränkung bei der Verwendung von DI mittels Dagger. Wie vielleicht schon aufgefallen ist, wird zum Abrufen der Component der Zugriff auf die Application benötigt. Indirekt also ein Zugriff auf den Context. Bis jetzt ist mir keine (elegante) Möglichkeit bekannt, wie dies aus einfachen Klassen wie bspw. der enthaltenen SecondClass möglich ist.
Wie sich in diesem Stackoverfloweintrag gezeigt hat, sollte man in solchen Fällen auf eine DI mittels Dagger verzichten und mit einem herkömmlichen Konstruktor arbeiten.