Android Small Talks: Wstrzykiwanie zależności poprzez Daggera 2

Android Small Talks: Wstrzykiwanie zależności poprzez Daggera 2
Wstrzykiwanie zależności jest wzorcem projektowym, którego głównym zadaniem jest uwolnienie naszego kodu od zależności. Jak wszyscy na pewno zdajemy sobie sprawę, kod, który ma minimalną ilość zależności jest zdecydowanie łatwiejszy do zarządzania oraz zmiany. Łatwiej jest też taki kod wykorzystać i przetestować.

Wstrzykiwanie zależności w praktyce

Zobaczmy w takim razie, w jaki sposób możemy zastosować ten wzorzec projektowy. Przykład z ukrytą zależnością:

public class Lamp{
	Bulb bulb;
	public Lamp(){
		bulb = new Led();
	}
}

W takim przypadku klasa Lamp jest zależna od żarówki typu Led. Nie jesteśmy w stanie podmienić jej na żaden inny typ żarówki w trakcie działania programu, czy podczas testowania naszego kodu. Możemy to zrobić lepiej:

public class Lamp{
	Bulb bulb;
	public Lamp(Bulb bulb){
		this.bulb = bulb;
	}
}

Co daje nam możliwość tworzenia lamp z różnymi żarówkami?

Lamp ledLamp = new Lamp(new Led());
Lamp halogenLamp = new Lamp(new Halogen());

Daje nam to możliwość łatwej podmiany implementacji w zależności od potrzeb. Więcej na temat wstrzykiwania zależności można poczytać tutaj.

Platformy umożliwiające wstrzykiwanie kodu

Powstały liczne biblioteki, które umożliwiają automatyzację procesu wstrzykiwania zależności. Do najpopularniejszych można zaliczyć:

  • guice,
  • dagger,
  • dagger2.

Każdy z tych frameworków działa na Androidzie.

Dagger 2

Dagger 2 jest biblioteką stworzoną przez Google’a. Do jej głównych zalet można zaliczyć:

  • generację kodu w trakcie kompilacji,
  • walidację poprawności klas inicjalizacyjnych w trakcie kompilacji,
  • dużą czytelność wygenerowanych klas,
  • możliwość podmiany implementacji do testów.

Dagger 2 bardzo dobrze się łączy z Gradlem. Aby zacząć naszą zabawę z wstrzykiwaniem zależności, musimy dodać do głównego pliku build.gradle naszego projektu poniższy kod:

classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

Następnie musimy dodać kilka linijek w pliku build.gradle naszego modułu (kod został skrócony ze względu na czytelność):

apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'

android {
    ...
}
dependencies {
    apt 'com.google.dagger:dagger-compiler:2.0'
    testApt 'com.google.dagger:dagger-compiler:2.0'
    androidTestApt 'com.google.dagger:dagger-compiler:2.0'
    provided 'org.glassfish:javax.annotation:10.0-b28' //Required by Dagger2 
}

Dagger 2 korzysta z powszechnie używanych adnotacji @Inject, która jest zdefiniowana w pakietach javax.annotation. Android nie implementuje tych adnotacji, stąd musimy je dostarczyć sami. Po tych przygotowaniach możemy się zabrać za pisanie kodu! 🙂

Komponent

Jest to główny element platformy. Komponenty pozwalają określić, do jakich klas będzie wstrzykiwany kod oraz jakie moduły będą dostarczały zależności. Przykładowy komponent, udostępniający nam kontekst aplikacji wgląda tak:

@Singleton
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
    Context context();

    Application application();
}

Komponent ten mówi nam tylko, jakie zależności będzie dostarczał. Implementację tego interfejsu wygeneruje w trakcie kompilacji Dagger 2. Komponentem jest klasa oznaczona przez adnotację Component. W adnotacji możemy podać moduły, które dostarczają nam zadeklarowane obiekty, jak i inne komponenty, od których zależny jest dany moduł (w tym przypadku ApplicationComponent nie jest zależny od żadnych innych komponentów).

W komponentach możemy zdefiniować, jak długo mają być przechowywane obiekty. Służy do tego adnotacja @Singleton, która mówi Daggerowi, że obiekty zadeklarowane w tym komponencie mają być przechowywane w trakcie działania aplikacji. W tak prosty sposób możemy zdefiniować w naszej aplikacji singleton, który będzie odporny na odtwarzanie aktywności. Wszystkie obiekty, które mają być singletonami muszą zostać zadeklarowane w komponencie oznaczonym adnotacją @Singleton.

Kolejny komponent jest już stworzony do wstrzykiwania zależności:

@Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {
    void inject(MainActivity activity);
}

Specjalna funkcja inject() definiuje klasy, w które będą wstrzykiwane zależności. Ważne jest, aby podawać tutaj implementacje klas, a nie klasy bazowe, aby Dagger wiedział, na którym obiekcie działać. W omawianym przykładzie zadeklarowana została zależność od ApplicationComponent. Dzięki temu wstrzykiwana klasa może korzystać z obiektów zadeklarowanych w zależnym komponencie.

Moduły

Moduły dostarczają implementację obiektów. Musimy jednak pamiętać o stworzeniu obiektów, do których nie mamy implementacji (przykładowo obiekty z bibliotek). Moduł aplikacji wygląda następująco:

@Module
public class ApplicationModule {
    protected final Application application;

    public ApplicationModule(Application application) {
        this.application = application;
    }

    @Provides
    Application provideApplication() {
        return application;
    }

    @Provides
    Context provideContext() {
        return application;
    }
}

Wszystkie funkcje, które są oznaczone adnotacją @Provides zostaną wykorzystane przez Daggera do utworzenia obiektów. W taki sposób możemy na przykład utworzyć obiekt klienta API.

Inicjalizacja komponentów

Warto pamiętać, że implementacje komponentów nie będą dostępne do momentu pierwszego zbudowania projektu. Mają one także specjalne nazwy: DaggerNazwaKomponentu. Aby zainicjalizować komponent aplikacji, musimy dodać następujący kod w naszej aplikacji:

public ApplicationComponent getComponent() {
        if (applicationComponent == null) {
            applicationComponent = DaggerApplicationComponent.builder()
                    .applicationModule(new ApplicationModule(this))
                    .build();
        }
        return applicationComponent;
    }

W powyższym przypadku DaggerApplicationComponent jest klasą wygenerowaną przez Dagger’a. Analogicznie musimy dodać kod w aktywności:

activityComponent = DaggerActivityComponent.builder()
                    .applicationComponent(MyApp.get(this).getComponent())
                    .activityModule(new ActivityModule(this)).build().inject(this);

Po wykonaniu tego kodu, w obiekcie MainActivity, jak i we wszystkich klasach, które aktywność posiada, zostaną utworzone nasze obiekty.

Gdy chcemy, aby obiekt stworzony przez nas brał udział w procesie wstrzykiwania, musimy oznaczyć jego konstruktor – adnotację @Inject:

public class DIObject {
  
    @Inject
    public DIObject() {
    }
}

Za każdym razem, kiedy będziemy chcieli użyć instancji obiektu wystarczy stworzyć:

@Inject
DIObject object

Dzięki temu Dagger będzie wiedział, w jaki sposób dostarczyć nam obiekt.

Dzięki wstrzykiwaniu zależności, kod, który będziemy pisać stanie się zdecydowanie bardziej przejrzysty. Wszystkie obiekty będą tworzone w wydzielonej dla nich klasie, co pozwoli na wyeliminowanie wielu powtórzeń. Nasze testy będą prostsze, a kod łatwiejszy w utrzymaniu.

Przykładowy kod, obrazujący wykorzystane tej techniki znajduje się na naszym GitHubie.

Wycena projektu

Sprawdź, jak wykorzystujemy naszą wiedzę w praktyce i stwórz z nami swój projekt.

Dlaczego warto rozwijać z nami projekty?

Logo Mobile Trends Awards

Mobile Trends Awards 2017

Nominacja w kategorii
M-COMMERCE

17

opinii klientów

Clutch logo
Logo Legalni bukmacherzy

Nagroda Legalnych Bukmacherów 2019

Najlepsza aplikacja mobilna

60+

zrealizowanych projektów