Przejdź do głównej zawartości

Dependency Injection w Windows Universal Apps (UWP)

Ostatnio zacząłem pisać aplikację na platformę Windows Universal Apps, która już jakiś czas temu mnie bardzo zaciekawiła. Poza tym jest to okazja aby się trochę rozwinąć i oderwać od świata javy ;). Aplikacja prawdopodobnie pojawi się niedługo na GitHub ale w związku z tym pojawi się pewnie kolejny post.
W trakcie pisania pojawia się jak to zwykle bywa mnóstwo problemów. Staram się rozwiązywać je sukcesywnie, a jedno z tych rozwiązać przedstawię w tym wpisie.

Problem

Okazuje się, że platforma UWP jest na tyle uboga, że brakuje w niej mechanizmów pozwalających na skorzystanie ze wstrzykiwania zależności. Dla mnie jest to jeden z najważniejszych wzorców, który chce mieć wdrożony w niemal każdej aplikacji, którą piszę. W sieci nie ma zbyt wielu materiałów na ten temat, szczególnie takich zrozumiałych dla mnie - programisty mającego mało dotychczas wspólnego z technologiami ze stajni Microsoftu. Znalazłem jednak fajne rozwiązanie, które jest proste i sprawdza się w moim przypadku. Zainspirowane zostało wpisem Dominika Webera na jego blogu.

Rozwiązanie

Aplikacja UWP składa się ze stron [ang. page]. Każda taka strona to jeden widok aplikacji. Celem jest wstrzyknięcie do danej strony wybranych zależności. Zależnościami do tych zależności zajmie się już wybrany kontener IoC. Strony są tworzone przez framework więc nie ma zbytnio możliwości zlecenia tworzenia stron do kontenera. Odpada zatem preferowana przeze mnie możliwość wstrzykiwania przez konstruktor. W takim wypadku jest możliwość wstrzykiwania właściwości [ang. property], którą umożliwia wybrany przeze mnie kontener IoC, o którym póżniej.
Ok, ale żeby wstrzyknąć coś do obiektu, trzeba mieć najpierw do niego referencje.Okazuje się, że najłatwiej o taką referencje z poziomu ramki [ang. frame]. Ramka ta jest tworzona w pliku App.xaml.cs i reprezentuje okno naszej aplikacji:
rootFrame = new Frame();
Okazuje się, że wystarczy rozszerzyć klasę Frame i nadpisać metodę OnContentChanged aby mieć dostęp do właśnie uruchamianej strony. W tym właśnie miejscu postanowiłem wstrzykiwać brakujące zależności do przełączanych stron.

Kontener

Wybrany przeze mnie kontener DI to Autofac. W moim przypadku, cała konfiguracja kontenera znajduje się w konstruktorze ramki, natomiast wykorzystanie go do wstrzyknięcia zależności we wspomnianej wcześniej metodzie OnContentChanged. Poniżej kod gotowej ramki:
public class AutofacFrame : Frame
{
    private readonly IContainer _container;

    public AutofacFrame()
    {
        var containerBuilder = new ContainerBuilder();

        //Dependencies registration
        containerBuilder.RegisterType<VideoLibraryVideoDownloader>()
            .As<IVideoDownloader>()
            .SingleInstance();
        containerBuilder.RegisterType<MediaTranscoderAudioExtractor>()
            .As<IAudioExtractor>()
            .SingleInstance();
        containerBuilder.RegisterType<YouTubeSearchEngine>()
            .As<ISearchEngine>()
            .SingleInstance();
        containerBuilder.RegisterType<SearchResultsVm>();
        containerBuilder.RegisterType<YouTubeSearchEngine>()
            .As<ISearchEngine>()
            .SingleInstance();
        containerBuilder.RegisterType<YouTubeApi>()
            .SingleInstance();
        containerBuilder.RegisterType<HttpClient>()
            .SingleInstance();
        containerBuilder.RegisterType<Settings>()
            .SingleInstance();
        //Dependencies registration

        _container = containerBuilder.Build();
    }

    protected override void OnContentChanged(object oldContent, object newContent)
    {
        base.OnContentChanged(oldContent, newContent);
        _container.InjectUnsetProperties(newContent);
    }
}
I tworzenie ramki aplikacji zmieniamy na:
rootFrame = new AutofacFrame();
Metoda InjectUnsetProperties pozwala wstrzyknąć zależności do wszystkich właściwości strony, które mają wartości null. Należy również pamiętać aby zapewnić settery dla tych właściwości. U mnie wygląda to tak:
private Settings _settings;

public Settings Settings
{
    set => _settings = value;
}
Póki co rozwiązanie uważam za spełniające wymagania, a przy tym bardzo proste ;)

Komentarze

Popularne posty z tego bloga

Aplikacja czasu rzeczywistego w Spring i AngularJS

Naszła mnie potrzeba aby utworzyć pewien dashboard, który reagowałby na zmiany przychodzące z serwera. Jako, że tworzę swoje aplikacje z wykorzystaniem Spring Boot (backend) i AngularJS (frontend) to o komunikacje serwer -> klient należy zadbać samemu. Po pospiesznej analizie tematu okazało się, że taką komunikację zapewnia protokół WebSockets (co dla niektórych może jest oczywiste, lecz dla mnie nie było). Co trzeba zrobić? Zestawić połączenie pomiędzy serwerem, a klientem i przesyłać wiadomości. Proste? Proste. No to jazda.
Projekt Kombinacja Spring Boot i AngularJS (angular w wersji 1) tak mi się spodobała, że utworzyłem sobie własny archetyp mavena, aby szybko móc tworzyć kolejne proste aplikacje. Jest on dostępny na moim GitHubie. Instrukcje tworzenia z niego projektu są w Readme, nie będę się powtarzał.
Eventy Taka aktualizacja danych musi być wywoływana przez jakieś zdarzenie. Na potrzeby przykładu wykorzystałem springowe eventy. Eventem w springu może być zwykłe POJO. W moi…

Immutable collections - Java

Mierzenie się z kolekcjami i poprawnym ich wykorzystaniem jest problematyczne dla początkujących programistów. Jak często widzieliście w jakimś kodzie fragment typu:
cart.getProducts().add(product); Sam pisałem niegdyś takie brzydkie rzeczy. W czasie, w którym wyrabiałem sobie moje skromne doświadczenie wypracowałem sobie jednak pewne zasady jak postępować z kolekcjami. Ktoś mógłby się ze mną spierać - nawet podając sensowne argumenty ale w mojej pracy trzymanie się tych nawyków sprawdza się i na razie się ich trzymam.
 W czym problem? Przedstawiony wyżej fragment kodu to wyraźne złamanie zasady enkapsulacji. Skoro obiekt udostępnia swoje wnętrze (w typ przypadku jakąś kolekcję) i można z tym robić wszystko co się nam spodoba to po co te wszystkie modyfikatory dostępu i po co w ogóle taki obiekt? Skoro obiekt, który posiada jako swoją właściwość (property) jakąś listę i udostępnia ją tak po prostu na zewnątrz to może okazać się, że biedny obiekcik jest nieświadomy, że ktoś mu grzebie w…

Dlaczego default jest blee... Wielodziedziczenie

Piszę ten post gdyż złapałem się na tym, że nie potrafiłem uargumentować swojego przekonania dotyczącego pewnego aspektu programowania gdy padło pytanie: "dlaczego?". Dopiero później wszystko sobie przypomniałem i mam nadzieję, że ten post nie pozwoli mi już o tym zapomnieć ;).

Dawno, dawno temu, gdy ukazała się Java w wersji 8, wielu z nas zachwycało się nowym mechanizmem: metody default! Bo to takie fajne, nowe, można ograniczyć powtarzanie się kodu, no generalnie bajer. Dopiero po chwili niestety, dotarło, że wprowadzenie takiego rozwiązania to cofanie się w czasie.
 Wielodziedziczenie Podczas lat rozwoju języków programowania pojawił się w pewnym momencie trend aby unikać stosowania rozwiązania zwanego wielodziedziczeniem. Czym jest ten mechanizm? Pozwala on na dziedziczenie z wielu klas bazowych jednocześnie. Unikanie jego stosowania, a nawet unikanie implementacji takiego mechanizmu w nowoczesnych językach jest poparte sensownymi argumentami.

Po pierwsze, może wydarzy…