Artykuł

freeimages.com freeimages.com
gru 13 2014
0

Współdzielenie kodu i zasobów w Universal Apps

W ostatnim czasie otworzyłem mały cykl wpisów na temat Universal Apps, ponieważ jest to obecnie dość bliski mi zawodowo temat. Sama koncepcja tego rozwiązania, wydaje się być odważna, ale wygląda jednak na to, że ktoś to w Microsofcie całkiem sprytnie to przemyślał. Oczywiście kilka elementów jeszcze tutaj kuleje, ale w tej chwili projekty Universal Apps przypominają trochę tworzenie stron responsywnych. Mamy jednolity backend i tak naprawdę zmienia się głównie frontend (XAML), choć w tym akurat przypadku programiści XAMLa mają trochę lepiej niż frontendowcy;-)

Dlaczego o tym wszystkim znów piszę? A no dlatego, że tym właśnie wpisem chciałbym usystematyzować wiedzę co, gdzie i jak współdzielić w projektach Universal Apps.

Zanim zaczniesz...

Zanim zaczniesz tworzyć aplikacje uniwersalne, które z założenia mają współdzielić jak największą ilość kodu, powinieneś wiedzieć jedną rzecz - ten cel uda się osiągnąć tylko mając dobry plan, a dobry plan w przypadku Universal Apps powinien uwzględniać użycie wzorca MVVM. Oczywiście nie musisz w takim przypadku implementować wszystkiego sam. Możesz skorzystać z gotowych bibliotek, do których tylko przypniesz swój kod;) Poniżej lista najpopularniejszych bibliotek tego typu:

Co dają nam takie biblioteki? Przede wszystkim kontrolę. Dzięki MVVM możemy umieszczać kod przetwarzający dane oraz realizujący określone zadania z dala od widoku. W widoku zaś w takim przypadku odwołujemy się do konkretnego ViewModelu, który zawiera przetworzone dane i jest w stanie reagować na nasze akcje na formatce. W idealnie napisanej aplikacji, moglibyśmy praktycznie wyrzucić cały code-behind naszych stron i pozostawić tam tylko standardowe, spójne dla obu platform deklaracje metod. Warto więc już na samym początku projektu pomyśleć o architekturze całej aplikacji.

Więcej o MVVM oraz o MVVM Light pisałem w jednym z ostatnich wpisów.

Gdzie umieścić kod?

Gdy już wstępnie zaplanujemy architekturę naszej aplikacji, warto jest przystąpić do działania. Zakładając że naszym celem jest współdzielenie jak największej ilości kodu, mamy teraz trzy wyjścia:

  • Umieszczamy cały współdzielony kod w części Shared projektu
  • Umieszczamy współdzielony kod w bibliotekach Portable (PCL)
  • Mieszamy oba rozwiązania

Oczywiście najlepszym wariantem jest opcja trzecia.. do której jednak w praktyce dochodzimy po kilku zrealizowanych projektach, z których dało się wyciągnąć część wspólną, bądź też w sytuacji gdy pewien wycinek kodu wykorzystywany jest również w innych projektach .Net.

Nawiązując do innych projektów .Net, warto tu również wspomnieć, że muszą mieć one odpowiedni TargetType by móc funkcjonować w świecie Universal Apps.

Co współdzielić?

Współdzielić możemy wiele elementów. Mogą to być całe strony, pojedyncze klasy, style, pliki zasobów, a nawet grafiki. Tak na dobrą sprawę można umieścić kod całej aplikacji w części Shared, aczkolwiek prawie nigdy nie jest to możliwe. Praktycznie zawsze trzeba stworzyć inne ekrany na Windows/Windows Phone, a także zapewnić odpowiednie grafiki dla aplikacji mobilnej i tabletowo-komputerowej. Nie ma się tu bynajmniej co obrażać na Microsoft, ponieważ najzwyczajniej w świecie nie da się zrobić aplikacji która miałaby więcej niż jeden element i wyświetlała się tak samo dobrze na smartfonie jak i w rozdzielczości Full HD na komputerze.

Mimo wszystko można jednak naprawdę dużo kodu wydzielić, o czym napiszę szerzej w kolejnych akapitach.

Wspólne pliki

Pisząc kod aplikacji uniwersalnych, warto zwrócić uwagę na fakt, że wszystkie elementy naszej bazowej solucji, współdzielą namespace. W praktyce wygląda to tak, że z tej samej przestrzeni nazw skorzystamy w projekcie:

  • Windows
  • Windows Phone
  • Części Shared

Część Shared jest automatycznie podpięta do dwóch pozostałych projektów, więc jeśli tylko dodamy do niej jakiś plik/zasób, to możemy z niego bez większych problemów korzystać w całej naszej aplikacji. W praktyce część Shared jest włączona do naszych projektów na etapie kompilacji i tak naprawdę jest to taki trochę wirtualny byt w naszej solucji.

Współdzielić możemy naprawdę sporo, w tym nawet bazowy plik aplikacji, czyli - App.xaml;-)

Co zrobić w sytuacji gdy w kodzie współdzielonego pliku, pewien element zachowuje się inaczej w Windows, a inaczej w Windows Phone? Bynajmniej nie zawsze musimy w takiej sytuacji rozdzielać te pliki i wrzucać do osobnych projektów.. wciąż możemy współdzielić większość kodu, a te specyficzne fragmenty wydzielić za pomocą dyrektywy preprocesora:

public void MyNotUniversalMethod()
{
    // shared code
#if WINDOWS_PHONE_APP
    // windows phone code
#elif WINDOWS_APP
    // windows code
#endif
    // more shared code
}

Partial klasy

W pewnych określonych sytuacjach, może zdarzyć się tak, że chcielibyśmy współdzielić kod, ale różnic między platformami jest na tyle dużo, że dodanie kilku - kilkunastu dyrektyw preprocesora zaciemniło by kod. Co zrobić w takiej sytuacji? Stworzyć partial klasę;-) Partial klasy to tak naprawdę klasyczne klasy, których kod został rozbity na kilka plików. Taka klasa zyskuje swoją pełną postać (osobowość) w momencie kompilacji (IntelliSense również sobie z nimi radzi). Jak to w praktyce wygląda? Spójrzmy na Solution explorer:

W całej solucji dodałem trzykrotnie klasę MyClass i przy każdej deklaracji wykorzystałem słowo kluczowe Partial. Następnie w części Shared dodałem następujący kod:

public partial class MyClass
{
    public MyClass()
    {
    }

    public void SharedMethod()
    {
        this.NotSharedMethod();
    }
}

Natomiast w projekcie Windows:

public partial class MyClass
{
    public void NotSharedMethod()
    {
        // Windows specific code
    }
}

I w Windows Phone:

public partial class MyClass
{
    public void NotSharedMethod()
    {
        // Windows phone specific code
    }
}

W prosty i szybki sposób napisaliśmy kod, który ma pewną część wspólną oraz określone fragmenty specyficzne dla platformy. Kompilator natomiast złączy to w dwie zgrabne klasy - jedna dla projektu Windows, druga dla Windows Phone. Czyż nie jest to świetne rozwiązanie;-)?

Tworząc partial klasy musimy jednak pamiętać o jednej ważnej rzeczy - nie możemy dublować kodu. Przez dublowanie kodu chodzi mi o to, że w dwóch (lub większej ilości) plikach, które mają być docelowo złączone w jeden wspólny byt, nie może znaleźć się dwa razy ten sam kod. Czyli np. konstruktor może pojawić się tylko raz. Oczywiście w naszej sytuacji sprowadza się do tego, żeby klasa partial z projektu Windows [Phone], nie dublowała kodu z części Shared.

Partial klasy są też dość intensywnie wykorzystywane przez XAMLowe strony. W praktyce możemy zrobić tak, że cały code-behind strony będzie w części Shared projektu, natomiast pozostałe projekty będą tworzyć swoją warstwę wizualną (XAML). Taka kombinacja również zadziała, co mogliście zobaczyć w tym wpisie.

Biblioteki

Współdzielić możemy również wykorzystywane biblioteki, które zainstalowaliśmy np. za pomocą NuGeta. W tym przypadku żeby wszystko działało właściwie, biblioteki należy aktywować zarówno dla projektu Windows jak i Windows Phone. Gdy tego dokonacie, to staną się one automatycznie dostępne w części Shared, do której formalnie nie można dodać żadnych bibliotek ani referencji.

Oczywiście możemy dodawać również biblioteki do wybranych projektów, z tym że w takiej sytuacji nie skorzystamy z nich w części współdzielonej Shared o ile nie będziemy wykorzystywać tam dyrektyw preprocesora i tym samym kierunkować kod pod kątem wybranego OSu.

Teksty

W jednym z ostatnich wpisów, pisałem na temat plików RESW. Dla przypomnienia, w plikach resw możemy umieścić stringi wykorzystywane w naszej aplikacji. Warto korzystać z tego rozwiązania również gdy nie planujemy różnych wersji językowych, ponieważ nawet dla jednego języka, unikamy powtarzania tekstów w kodzie dla obu projektów;-)

Czego nie współdzielić?

Wiele rzeczy możemy współdzielić, ale w przypadku niektórych tematów lepiej sobie odpuścić. Poniżej kilka przykładów.

Zasoby

Pliki zasobów (Assety) możemy również współdzielić, aczkolwiek nie jest to do końca zalecane. Fakt jest taki, że aplikacje dla Windows Phone/Windows, mają po prostu inny układ i rozdzielczość, więc siłą rzeczy trudno jest przygotować jednolite grafiki, które w spójny sposób zadziałają w obu projektach. Kwestią do rozważenia, jest wykorzystanie wspólnych grafik dla pewnych ikon, czy obrazków, które z założenia mają mieć pewien, sztywny rozmiar w obu projektach.

Widoki

Widoki bardzo trudno jest współdzielić, ponieważ ciężko jest zrobić widok, który dobrze będzie zachowywać się na ekranie o szerokości 400 pikseli na telefonie, jak również w rozdzielczości Full HD na komputerze. Na siłę można doprowadzić do sytuacji w której widok dla obu platform pozornie będzie wyglądał tak samo. Możemy bowiem zrobić style i kontrolki specyficzne dla każdej z platform i później umieścić je na jednym ekranie. Oczywiście w tle wszystko będzie inne. Jest to na pewno podejście warte rozważenia.

Style

Style to podobny temat jak w przypadku widoków, ponieważ oba te zagadnienia są ze sobą dość ściśle powiązane. Na szczęście na tym obszarze mamy trochę większe pole manewru, ponieważ style możemy umieszczać w plikach, które również będą zaczytywane w zależności od platformy. Spójrzmy na poniższy kod pliku App.xaml:

<Application
    x:Class="App6.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App6">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Styles/AppStyles.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

Według tego zapisu, nasz plik App.xaml będzie szukał pliku AppStyles.xaml w katalogu Styles w naszym projekcie. Spójrzmy zatem na Solution Explorer:

Każdy projekt ma swój własny plik styli, jednocześnie wczytywane są one przez aplikację z jednego miejsca. Pewnie się powtarzam, ale czy nie jest to kolejne świetne rozwiązanie;-)?

Oczywiście możemy dodać również plik który będzie wykorzystywany w obu aplikacjach. W takim przypadku należy stworzyć ten plik w projekcie Shared, a także dodać kolejny wpis do sekcji ResourceDictionary.MergedDictionares.

Wskazówka na koniec

Na koniec mała wskazówka. Pracując nad kodem w części Shared, domyślnie będziemy widzieć kod w kontekście wersji dla Windows. Oznacza to że jeśli dodamy dyrektywę z kodem dla Windows Phone, to ten kod zostanie wyszarzony i nie będzie możliwe korzystanie np. z IntelliSense. Na szczęście kontekst możemy łatwo zmienić, klikając na jedną z list rozwijanych w oknie Visual Studio:

Podsumowanie

Dzisiejszy wpis zawiera skrót informacji na temat współdzielenia kodu w świecie Universal Apps. Mam nadzieję, że będzie to dla Was dobry punktu startu. Jeśli macie jakieś pytania/uwagi/zastrzeżenia, to zapraszam do komentarzy.

Podoba Ci się ten wpis? Powiedz o tym innym!

Send to Kindle

Komentarze

blog comments powered by Disqus