Jak jsem zmínil v jednom z předchozích článků, jsem nadšený z příslibu Uno Platform – přinést Univerzální Platformu Windows na všechny platformy. Chtěl jsem s vývojem projektu pomoci přispěním do zdrojového kódu na GitHubu. Protože vím, že začít pracovat na podobně velkém projektu může být náročné, chci se v sérii článků podělit o kroky, které vedly k mému prvnímu Pull Requestu.
V této části se podíváme na základy – vysvětlíme si, jak Uno zapadá do světa multi-platformních frameworků, sestavíme Uno.UI na našem počítači, seznámíme se se zdrojovým kódem a vybereme si issue na které budeme pracovat.
Uno 101
Než začneme přispívat do Uno Platform, musíme pochopit její filosofii. Existuje mnoho multi-platformních frameworků. Dovolím si s klidem v duši ignorovat ty JavaScriptové a budu se soustředit na scénu C#. Začneme pohledem na Xamarin.Forms a Avalonia UI a pak se seznámíme s tím, jak se od nich Uno liší.
Xamarin.Forms má svůj vlastní XAML dialekt a ekosystém ovladacích prkvů a nástrojů. Framework na pozadí používá nativní ovladací prvky dané platformy s pomocí systému rendererů. Tato strategie bohužel vede k tomu, že funkcionalita kterou ovladací prvky v XAMLu nabízí je často pouze průnikem toho, co nabízí všechny platformy. Kromě toho, rozdíly v XAML dialektu mohou být matoucí pro vývojáře, kteří přichází z platforem WPF nebo UWP. Komunita Xamarin.Forms je však velmi aktivní a existuje množství knihoven s ovladacími prvky a nástroji, takže pokud potřebujete něco, co Xamarin.Forms zatím neumí, pravděpodobně to najdete na GitHubu.
Avalonia UI také má svůj vlastní XAML dialekt postavený na WPF. V případě ovladacích prvků volí úplně opačný přístup než Xamarin.Forms – používá custom rendering kdekoliv je to možné. Přestože to zajistí stejné UI na všech zařízeních, u některých ovladacích prvků pak chybí nativní “feeling” který uživatelé očekávají.
Jak to vidí Uno Platform
Uno Platform zvolila UWP XAML dialekt nejen jako základ, ale s cílem 100% kompatibility – včetně skvělých vlastností jako jsou AdaptiveTriggers
a control templating. Díky kompatibilitě mohou vývojáři vyvíjet proti UWP a využívat všechny nástroje, které Visual Studio nabízí. Na pozadí se používají nativní ovladací prvky dané platformy pro zajištění nativního chování, ale vzhled samotný je plně přizpůsobitelný s cílém poskytnout jednotné UI.
Uno ale nekončí u UI. Uvědomuje si i další oblast, která je při vývoji multiplatformních aplikací náročná – API specifická pro platformu (např. práci s hardware). Namísto toho, aby vývojář musel psát platformně závislý kód, může použít “nativní” Windows API jako je Geolocator
, Launcher
a další, která jsou implementována v Uno Platform a na pozadí volají nativní API cílové platformy. Skvělé na tom je, že jakmile znáte UWP, můžete napsat hodně kódu bez toho, abyste museli přemýšlet nad rozdíly mezi platformami a platformně zavislý kód psát pouze pro API která Uno zatím nepodporuje.
Vytvoření Uno aplikace
Vytvořme si nový projekt pomocí Uno Platform. Nejprve musíme nainstalovat do Visual Studia rozšíření Uno Platform Solution Templates přes menu Tools – Extensions and Updates (VS 2017) nebo Extensions – Manage Extensions (VS 2019).

Poté, co máme rozšíření nainstalované, klikneme na File – New Project a vybereme šablonu Cross-Platform Uno App:

Výsledkem je řešení s několika projekty pro cílové platformy a jeden sdílený projekt který všechny referencují. Platformní projekty obsahují pouze minimum nutné pro funkgování aplikace a aplikační logika a UI patří do Shared projektu:

Ve sdíleném projektu najdeme soubory App.xaml
a MainPage.xaml
, které bychom obvykle očekávali v nové UWP aplikaci. Shared projects jsou vlastně takové “balíčky souborů”, které jsou při kompilaci přidány do projektu, který je referencuje. Nejde o referenci jako v případě DLL knihoven a Shared projekt tak nemůže fungovat sám o sobě. API, která může používat jsou určena projektem, který jej referencuje.
Projekty pro Android, iOS a WASM všechny referencují NuGet balíček Uno.UI
, který poskytuje jmenné prostory Windows
včetně ovladacích prvků a vlastních implementací mnoha UWP API.
UWP projekt se od ostatních trochu liší – neobsahuje žádnou referenci na Uno.UI . Jednoduchým důvodem je fakt, že to není třeba – UWP již zahrnuje jmenný prostor Windows
, takže všehny odkazy v Shared projektu automaticky budou využívat nativní implementaci UWP API. Díky tomu nejsme nikdy omezováni – pokud v Uno nějaká funkčnost zatím chybí, UWP projekt bude přesto fungovat na 100 %. Navíc můžeme nejprve vyvíjet proti UWP projektu a používat všechn nejnovější nástroje které Visual Studio nabízí včetně Designeru, XAML Live Edit & Continue, atd.
Sestavení Uno.UI
Pojďme se teď podívat na zdrojový kód Uno platform. Začneme vytvořením forku Uno repository a naklonováním na disk. Prvním krokem je sestavení samotného Uno.UI řešení.
Pro to musíme nejprve mít ve Visual Studiu nainstalované všechny potřebné workloady a SDK. Naštěstí je vše potřebné popsáno v dokumentaci Una, takže můžete postupovat podle uvedených instrukcí.
V mém případě sestavení řešení na první pokus neprošlo a musel jsem nejprve samostatně sestavit projekt Uno.UI.BindingHelper.Android a pak znovu celé řešení. Pokud narazíte na jiné problémy, určitě se neváhejte zeptat na Uno Platform Gitteru – tým je velmi přátelský a odpovídá rychle!

Abychom otestovali, že vše funguje správně, spustíme si SamplesApp. Najdeme zde projekt pro každou podporovanou platformu a jeden Shared projekt. Doporučuji spustit Android nebo iOS variantu, protože mají nejširší podporu API po UWP, které ale používá přímo nativní UWP API, takže není zrovna nejlepším příkladem pro testování vašeho vývojového prostředí.

Průvodce zdrojovým kódem
Můžeme začít obecným pohledem na celé řešení. Zde je screenhot jeho hlavních složek:

Podívejme se na každou složku zvlášť:
- _Build – obsahuje projekt Uno.UI.Build se skripty,
nuspec
a dalšími zdroji které se využívají pro sestavení Una - Addins – nyní obsahuje projekt Uno.UI.Lottie který obsahuje experimentální podporu pro Lottie animace
- SamplesApp – ukázková aplikace pro všechny podporované platformy (zároveň slouží pro UI testy)
- SolutionTemplate – šablona pro File -> New Project kterou získáte instalací rozšíření Uno pro Visual Studio
- Tools – různé nástroje a generátory
- Uno.UI – hlavní projekty Una, včetně Uno a Uno.UI které obsahují většinu logiky
- Uno.Xaml – implementace XAMLu která odpovídá UWP XAMLu
Možná jste si všimli, že zde nenajdeme soro žádné “platformně-specifické” projekty – máme to pouze Uno a Uno.UI. Jak to celé tedy funguje? Uno používá multi-targeting. Když otevřeme Uno.csproj
, uvidíme následující:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<Project Sdk="MSBuild.Sdk.Extras" ToolsVersion="15.0"> | |
<PropertyGroup> | |
<TargetFrameworks>xamarinmac20;xamarinios10;MonoAndroid90;net461;netstandard2.0</TargetFrameworks> | |
<TargetFrameworksCI>MonoAndroid80;MonoAndroid90;xamarinios10;net461;netstandard2.0;xamarinmac20</TargetFrameworksCI> | |
</PropertyGroup> | |
… | |
</Project> |
Element <TargetFrameworks>
obsahuje výčet platform které Uno podporuje. V csproj
jsou některé výchozí reference které jsou společné pro všechny frameworky, ale pak zde najdeme i platformně-závislé reference:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<ItemGroup Condition=" '$(TargetFramework)' == 'MonoAndroid80'"> | |
<PackageReference Include="Xamarin.Android.Support.v4"> | |
<Version>26.1.0.1</Version> | |
<PrivateAssets>none</PrivateAssets> | |
</PackageReference> | |
<PackageReference Include="Xamarin.Android.Support.v7.AppCompat"> | |
<Version>26.1.0.1</Version> | |
<PrivateAssets>none</PrivateAssets> | |
</PackageReference> | |
</ItemGroup> |
V této ukázce zjišťuje Condition
attribut, aby uvedené reference byly kompilovány pouze proti platformě MonoAndroid80
.
A co se zdrojovým kódem? Můžeme si povšimnou dalšího zajímavého elementu v .csproj
:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<Import Project="..\Uno.CrossTargetting.props" /> |
Tento soubor props
definuje kompilační konstanty pro jednotlivé platformy:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<PropertyGroup Condition="$(IsXamarinIOS)"> | |
<DefineConstants>$(DefineConstants);IOS1_0;XAMARIN;XAMARIN_IOS;XAMARIN_IOS_UNIFIED</DefineConstants> | |
</PropertyGroup> |
Jednodušší API mohou být implementovány s jejich pomocí. Skvělým příkladem je Windows.System.Launcher
:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static partial class Launcher | |
{ | |
public static async Task<bool> LaunchUriAsync(Uri uri) | |
{ | |
try | |
{ | |
#if __IOS__ | |
return UIKit.UIApplication.SharedApplication.OpenUrl(new global::Foundation.NSUrl(uri.OriginalString)); | |
#elif __ANDROID__ | |
var androidUri = global::Android.Net.Uri.Parse(uri.OriginalString); | |
var intent = new global::Android.Content.Intent(global::Android.Content.Intent.ActionView, androidUri); | |
((Android.App.Activity)Uno.UI.ContextHelper.Current).StartActivity(intent); | |
return true; | |
#elif __WASM__ | |
var command = $"Uno.UI.WindowManager.current.open(\"{uri.OriginalString}\");"; | |
var result = Uno.Foundation.WebAssemblyRuntime.InvokeJS(command); | |
return result == "True"; | |
#else | |
throw new NotImplementedException(); | |
#endif | |
} | |
catch (Exception exception) | |
{ | |
if (typeof(Launcher).Log().IsEnabled(LogLevel.Error)) | |
{ | |
typeof(Launcher).Log().Error($"Failed to {nameof(LaunchUriAsync)}.", exception); | |
} | |
return false; | |
} | |
} | |
} |
Na základě konstanty je vykonán správný platformně-specifický kód.
Uno.CrossTargetting.props
také referencuje další soubor PlatformItemGroups.props
, který zajišťuje, že pro konkrétní cílové platformy budou pro kompilaci zahrnuta pouze podmnožina souborů se zdrojovým kódem pomocí speciálních sufixů v názvu souboru:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<None Include="**\*.iOS.cs" | |
Exclude="bin\**\*.iOS.cs;obj\**\*.iOS.cs" /> | |
<Compile Remove="**\*.iOS.cs" /> | |
<Compile Include="**\*.iOS.cs" | |
Exclude="bin\**\*.iOS.cs;obj\**\*.iOS.cs" | |
Condition="$(_IsIOS)" /> |
Příkladem může být API Windows.Graphics.Display.BrightnessOverride
jehož implementace se skládá z následujících souborů:
- BrightnessOverride.cs – logika společná pro všechny platformy
- BrightnessOverride.iOS.cs – implementace pro iOS
- BrightnessOverride.Android.cs – implementace pro Android
- BrightnessOverride.wasm.cs – implementace pro WASM
Výběr issue
V sekci Issues v repozitáři Uno.UI najdeme vždy množství aktivních bugů, návrhů nových funkcí a vylepšení. Pokud si nejste jistí obtížností konkrétní issue, můžete se vždy zeptat na Gitteru a tým Una vám nějakou úlohu doporučí.
Já získal tip na svoji první issue od samotného architekta Una Jérôme Labana, a můžete ji najít zde.

Shrnutí
V této části našeho prvního dobrodružství Unem jsme si prošli vše podstatné do začátku. Nyní máme lepší představu jak do sebe vše zapadá a máme vybranou issue. V dalším článku se budu soustředit na to, jak jsem postupoval při implementaci vlastností třídy DisplayInformation
pro iOS a Android.
Super, vypadá to hezky. Uno je přesně ta věc, jejíž filosofie se mi zamlouvá nejvíc – UWP first a pak to s co nejmenším úsilím překlápět jinam.
Na to jak je projekt promyšlený a hezky zdokumentovaný mi přijde příliš malý (ani 1k hvězd na gh). Klasicky se bojím toho, že to bude oneman show a když si to hlavní contributor rozmyslí, tak je s celým unem veta. Zde je to trochu odlišné: je to spíš onecompany show, tedy vyvíjí to společnost nventive především pro své vlastní potřeby. Tím se trochu řeší problém s financováním (nikdo moc nechce dělat na OS projektu dlouhodobě zadarmo), ale budoucnost una závisí na osudu společnosti nventive… Oprav mě, jestli to vidíš jinak…
Jinak článek je skvělý, díky za podrobný vhled, už se těším na pokračování !
Ano, UWP first strategie je skvělá. Z mobilních platforem má podle mě nejpřívětivější API.
Je třeba vzít v úvahu to, že není open-source ani rok, ještě je hodně vývojářů, kteří o něm ještě ani neví. Potenciál to rozhodně má, hlavně ve chvíli, kdy se o něm podaří přesvědčit třeba současné uživatele Xamarin.Forms. Paradoxně tomu poslední dobou trochu sami napomáhají, protože trochu upadá podpora UWP v XF – na rozdíl od Androidu a iOS nedostalo nové funkce jako Visual a Shell, což těm, kteří UWP potřebují dá důvod poohlédnout se někde jinde. Nventive zřejmě za projektem hodně stojí a přesto, že většina commitů je od Jerome Labana, myslím že je jen otázka času, kdy se toho chytne i komunita a projekt se rozjede ještě více.
V ideálním světě by Microsoft měl Nventive koupit a Uno mít jako alternativní offering k Xamarin.Forms. Pokud by se tohle stalo, měl by Uno tým nejen jisté financování ale i přístup ke zdrojovým kódům UWP, takže by mnohem snadněji implementovat nové funkce tak, aby se chovaly přesně tak jak jsou implementovány na Windows. Myslím si, že když se Uno bude nadále rozvíjet a bude vidět zájem komunity, tak je celkem velká šance, aby tenhle krok Microsoft skutečně udělal. Oni potřebují aby UWP uspělo, takže by tohle byl naprosto přirozený krok.