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

Spring Data - save vs saveAndFlush

Cześć, dzisiaj będzie znowu trochę o warstwie persystencji. Czasami kodzie aplikacji korzystającej ze Spring Data można napotkać użycia metody repozytorium save , a czasami saveAndFlush , a z kolei innym razem brak jakiejkolwiek z nich podczas zapisu obiektu. Wszystkie trzy metody mają swoje zastosowanie choć nieco się różnią. Teoria W teorii, różnica jest prosta. Metody save oraz saveAndFlush dodają obiekt do kontekstu persystencji danej sesji i zwracają obiekt zarządzany. Ponadto druga z nich wymusza wymusza wykonanie nagranych przez ORM akcji na bazie danych przez co dane zostają przesłane do silnika bazy. Może się to okazać przydatne w przypadku gdy w ramach jednej transakcji chcemy jeszcze wykonać kolejne zapytania, które mają być świadome wprowadzonych wcześniej zmian. Natychmiastowa synchronizacja może się również przydać gdy wykorzystujemy poziom izolacji READ_UNCOMMITTED . Czasami jednak w kodzie nie ma żadnej z nich. Wtedy możliwe jest wykonywanie tylko zapytań UPDATE

Pułapki logowania w Javie

Czy spotkałeś kiedyś się z kodem takim jak poniższy? if (logger.isDebugEnabled()) { logger.debug("Request: " + requestObject); } Ja tak. Jakiś czas temu napotkałem na mnóstwo takich fragmentów w kodzie, który analizowałem. Zastanowiło mnie po co została wykorzystana tutaj instrukcja warunkowa - a jako, że if-y uwazam za zło konieczne postanowiłem to zbadać. Czy nie do tego właśnie służą biblioteki do logowania i udostępniane przez nie metody jak debug żeby właśnie sterowane konfiguracją decydowały co zalogować a co nie? Co więcej, okazało się, że instrukcję warunkową wprowadził ktoś w ramach większej akcji. I jak się okazuje, prawdopodobnie znacznie zwiększył dzieki temu wydajność aplikacji. W dzisiejszym wpisie postaram się wyjaśnić jak korzystać z loggera aby nie zaszkodziło to wydajności. Po co if-y? Na początek wyjaśnić trzeba czemu ten if tak na prawdę służy. Korzystamy z biblioteki do logowania i spodziewamy się, że wykorzystujac wybraną metodę, zostanie

Sneaky throws, czyli checked i unchecked exception w jednym!

Często podczas pisania kodu Java korzystam z wyrażeń lambda wprowadzonych wraz z Java 8. Często prowadzi to też do pewnych nowych problemów i zmusza do szukania rozwiązań. Przyjrzyjmy się jednemu z nich. Checked exception w wyrażeniu lambda Często zdarza się, że metoda wywoływana wewnątrz lambdy rzuca wyjątek. Nie ma sprawy gdy wyjątek jest obiektem klasy będącej podklasą RuntimeException . Wtedy po prostu się nim nie przejmujemy. Wyjątek jest przekazywany w górę stosu wywołań. Problem zaczyna się gdy kompilator zmusza nas do obsługi wyjątku. Jak zwykle mamy 2 wyjścia: obsłużyć wyjątek za pomocą bloku try-catch , bądź zadeklarować przekazanie wyjątku dalej za pomocą słowa kluczowego throws . O ile wiemy co zrobić po złapaniu wyjątku to wszystko gra. Co natomiast gdy chcemy wybrać drugą opcję? Większość interfejsów stosowanych jako typy parametrów, często przekazywanych jako lambdy jak np: Function , Consumer czy Supplier nie deklarują, że mogą rzucić wyjątek. W takim wypadku druga