This tutorial will help you build a dictionary app using Uno Platform.
Along the way, you will also learn about Uno Platform and its amazing features.
Before starting with the tutorial, let me share some details about Uno Platform, its main features and capabilities, updates and other important information.
What is Uno Platform?
Uno Platform is an open-source cross-platform GUI framework in .NET.
It helps you build pixel-perfect apps targeting different platforms with a single codebase
Uno Platform apps have a native-like performance with a consistent UX/UI across the different platforms
Technologies
Uno Platform leverages WinUI and WebAssembly technologies to run apps natively on iOS, Android, Linux, macOS, and web browsers (through WebAssembly).
WinUI is the state of the art UX framework to help you build apps for desktop and universal windows platform.
Design Markups
Uno Platform UI components can be written using
XAML
eXtensive Markup language, is a declarative XML-based language, that you can use to build UI for .NET apps.
StackPanel in XAML
C# Markup
is a set of fluent helper methods and classes to help you write declarative UI components using only C#. It was added in Uno Platform 5.0.
StackPanel in C# Markup
Uno Platform Introduced MVUX
Model-View-Update-eXtended
A modern and reactive style approach to architect and manage the state of your Uno application
It is an extended version of the MVU pattern (aka Elm Architecture) with the immutability, declarative presentation and asynchronous support
MVUX is comprised of 4 parts:
Model
The Model is represented by the MainModel class which is defined as a record, to guarantee immutability and inside this record we will have the main properties as State and Feed
These are the key concepts and components of MVUX, since you can use State to bind and manage the state of any control, such as TextBox and have a two-way data binding with it.
Feed is another important feature, where you can bind it with an MVUX FeedView, and using an asynchronous calls you can update the UI with incoming streams to the feed. The feed is similar to the observable in MVVM.
View
This is what the user sees, it is the visual representation of your application. In Uno Platform, the view view can be either built using XAML markup, which is an XML-based markup that is designed for building screen interfaces.
Or, you can write pure C# code to display UI components. This is called C# Markup, it is a declarative way to initialize and configure your UI component directly from the code written in C#.
Update
The event that takes place after a user interacts with the UI, either by entering text into the TextBlock, pressing on button, navigating to another screen and so on, as well the update can also be induced by background service like notification or any update from the hardware itself.
eXtended
The extended part is represented by the abstractions IFeed and IState that allow seamless data binding between the model and UI via the bindable proxies
MVUX helps you manage the binding and state without the boilerplate that would normally include with MVVM
Model-View-Update-eXtended
Model-View-ViewModel /MVVM
You can still opt to use MVVM though, the current mostly used design pattern to architect your mobile apps.
MVVM is a pattern that is already adopted by Android native SDK, and is commonly used in iOS native SDK.
Hot Reload
Skyrocket your UI development experience with the Hot Reload feature
Run the project once and keep updating your UI on the fly without rebuilding
The hot reload feature is supported on almost all IDEs and operating systems, support for more environments is being added on the go.
Powered by .NET 8 and C# 12
Write code for Uno Platform using .NET and C# and get all the benefits of the latest and greatest .NET 8 and C# 12
Also you can implement new features of Uno Platform from .NET 9 Preview Releases
It simply ships in lockstep with .NET
And you can bring your own .NET packages and add them as part of your project and they should work seamlessly.
Build from anywhere using any IDE
Build at ease from any OS:
Windows, MacOS, Linux
Using any IDE:
VS 2022, VS Code, Rider
Extensions are readily available:
Smart Wizards To Get Started
Uno Platform offers smart tools to help developers kick-start their app development:
The Template Wizard for VS 2022
The Live Wizard for VS Code and Rider
Uno-Check Tool
It is an amazingly smart command line tool that helps you check if you have the right components installed on your machine to successfully develop Uno Platform Apps.
And Uno-Check command line to run automated checks which will make sure you have the necessary components to build Uno Platform apps.
Choice of Rendering
Uno Platform allows you to control how the UI elements are rendered.
Uno Platform has 2 strategies for rendering
- Native 2D drawing primitives for pixel-perfect rendering, these are for technologies where the native primitive rendering is available such as UIKit, Views, HTML. It can also include native behaviors like IME support, native control embedding, native accessibility support…etc.
- SkiaSharp, a cross-platform library to draw 2D graphics for .NET platforms, including Uno Platform. Now it is being used in Mac, Windows and Linux, later it will include mobile and web
Multiple Design Options
3 Design Systems are supported by Uno Platform:
- Fluent
- Material
- Cupertino
Also, Uno Platform has a Figma plugin so you can easily generate C# markup and XAML directly from your Figma designs.
Huge Libraries Availability
Thanks to the community, Uno Platform has a huge collection of libraries with great support.
Libraries range from Control Toolkit, presentation frameworks, to theming and design, and others.
Also Uno Platform allows for embedding .NET MAUI-specific controls from 3rd party vendors and .NET MAUI Community Toolkit via Uno.Extensions packages
Migration from Xamarin.Forms
Xamarin.Forms has reached its end of support on May, 2024
A natural evolution and the best continuation to Xamarin.Forms is through Uno Platform.
You can migrate your custom controls, animations, data bindings, and others.
The docs have a complete guide on how to migrate from Xamarin.Forms to Uno Platform
Uno Platform Metrics
- 80m+ NuGet Downloads
- 250+ Contributors
- 8.6k+ GitHub Stars
- 13k+ X Followers
Proactive Community
Uno Platform has a long public roadmap, and is welcoming new contributors to participate in developing and supporting this brilliant project.
Join 250+ GitHub superior contributors to help develop and keep Uno Platform the most productive platform in .NET.
A Discord community is also available for focused discussions and support.
Build a Dictionary app using the Uno platform
Preparing the development environment
This tutorial helps you build a modern cross-platform app using the latest Uno Platform 5.2, .NET 8 and Visual Studio 2022.
To avoid the hassle of checking IDE, tools and dependencies, Uno Platform has introduced Uno-Check.
It is an extremely helpful and smart .NET Command-line tool to kick-start your development with Uno Platform.
Just run the command ‘uno-check’ and see it do the magic of checking and preparing your development environment.
Preparing the Setup with Uno-Check
Uno-check is available for Windows, MacOS and Linux. As a prerequisite, you must have the latest stable .NET SDK installed, which is .NET 8 in the case of our tutorial.
To install (reinstall) uno-check, open your command-line and enter the following:
1 |
dotnet tool install -g uno.check |
Then continue with running uno-check tool:
1 |
uno-check |
It will check each prerequisite one by one, and provide you with actionable prompt in case it detects it is not installed or not up-to-date
See in this case I don’t have Python installed, so it will prompt my consent to install it for me:
Answering with yes (y), opens a browser to install Python from Microsoft Store
Once you install Python and continue, Uno-Check will move on to the next step and might show you another prompt:
And so on, the prompts will keep showing as long as the Uno-Check tool doesn’t find the right or recommended match installed, and would advise you to attempt the fix.
Otherwise, if everything is installed as per the Uno-Check tool verification, you will get a success message and so you will be ready to start developing apps using Uno Platform.
Preparing our Development Environment
If you’ve run the Uno-Check tool, it would have checked the latest update on the IDE and applied it.
So first let’s make sure you have the latest Visual Studio 2022 update:
Getting Started with Uno Platform
Before you can start write Uno Platform apps, you need to install the extension that is relevant to your IDE, in this tutorial we are using Visual Studio 2022, so you will need the extension from the Visual Studio Marketplace or you can just install it from Extension manager from within VS 2022
Restart VS 2022 so it will be ready upon creating a new project
Starting the project
We will use Visual Studio 2022, make sure you
Create a new Project
Choose a path and name and continue:
A wizard popup from the Uno Platform VS Plugin will show and guide you through the setup of creating your Uno App.
There are plenty of choices that you can make, including the target platforms, presentation architecture between MVVM or MVUX, choice of XAML or C# Markup, design system, cross-cutting concerns and so on.
We will mainly keep the recommended settings, you might just need to modify your package/bundle id in Application tab to have a unique id to be used when publishing your apps to the App Store or Google Play.
Once creation is complete, you will see the below confirmation and next steps window.
All combinations are great and provide a solid ground for building your amazing Uno app.
But for the purpose of this tutorial, I will be choosing the first option: XAML + MVUX combination.
Now you will get prompted to install some components as show in the below, ONLY if you haven’t run the Uno-Check Tool, since that tool will basically handle checking and installing the needed dependencies for you.
You might be asked to install more components that are related to your choice above:
Then try to build the solution.
You will be prompted to accept the Android SDK and dbt License Agreements
After that building again, you might be asked to install Python, you can go ahead and install it using the Microsoft Store:
https://apps.microsoft.com/detail/9p7qfqmjrfp7?hl=en-us&gl=US
Then whenever you compile, it should be successful.
Oxford Dictionaries Signup and Generating Credentials
We are building a dictionary app, so we will be relying on an online dictionary API which will retrieve dictionary entries for terms and other important information such as word phonetics, synonyms and origin of the word.
A top dictionary and thesaurus source online is Oxford Dictionary.
Let’s head on to developers.oxforddictionaries.com to sign up and obtain an app id and app key in order to access the dictionary. Then once you are logged in, go to the Credentials section:
Then choose to create an application.
It will generate for your an app id and app key:
Since this is a tutorial, we will use the sandbox environment, it is free to use for the first alphabet for 500 calls.
Now let’s do a quick test for our new integration with Oxford dictionaries to see how can we call the entries endpoint and what would be the response.
I will do that with the help of Postman, it is my favourite tool when it comes to testing anything related to APIs.
Searching for the word ‘account’ using the sandbox api environment of Oxford Dictionaries and using our newly generated app_id and app_key, will result in the below response:
In Visual Studio 2022, there is a nice feature that allows you to easily generate classes from json, just copy the response above and switch back VS 2022.
Inside the Models folder, create a new class and name it ‘DictionaryEntry’,
then go to Edit -> Paste Special -> Paste JSON as classes
You will notice that you get a complete classes representation for the Oxford Dictionaries API response.
Just make sure the root class matches the file name just to keep things consistent.
The final version of the classes should look like the below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
namespace MyDictionaryApp.Models; public class RootDictionaryEntry { public string id { get; set; } public Metadata metadata { get; set; } public List<Result> results { get; set; } public string word { get; set; } } public class Metadata { public string operation { get; set; } public string provider { get; set; } public string schema { get; set; } } public class Result { public string id { get; set; } public string language { get; set; } public List<Lexicalentry> lexicalEntries { get; set; } public string type { get; set; } public string word { get; set; } } public class Lexicalentry { public List<Derivative> derivatives { get; set; } public List<DictionaryEntry> entries { get; set; } public string language { get; set; } public Lexicalcategory lexicalCategory { get; set; } public string text { get; set; } } public class Lexicalcategory { public string id { get; set; } public string text { get; set; } } public class Derivative { public string id { get; set; } public string text { get; set; } } public class DictionaryEntry { public List<string> etymologies { get; set; } public List<Pronunciation> pronunciations { get; set; } public List<Sens> senses { get; set; } } public class Pronunciation { public string audioFile { get; set; } public List<string> dialects { get; set; } public string phoneticNotation { get; set; } public string phoneticSpelling { get; set; } } public class Sens { public List<Construction> constructions { get; set; } public List<string> definitions { get; set; } public List<Example> examples { get; set; } public string id { get; set; } public List<Semanticclass> semanticClasses { get; set; } public List<string> shortDefinitions { get; set; } public List<Synonym> synonyms { get; set; } public List<Thesauruslink> thesaurusLinks { get; set; } public List<Note1> notes { get; set; } public List<Subsens> subsenses { get; set; } public List<Domainclass1> domainClasses { get; set; } public List<Domain> domains { get; set; } public List<Variantform> variantForms { get; set; } } public class Construction { public string text { get; set; } } public class Example { public string text { get; set; } public List<Note> notes { get; set; } } public class Note { public string text { get; set; } public string type { get; set; } } public class Semanticclass { public string id { get; set; } public string text { get; set; } } public class Synonym { public string language { get; set; } public string text { get; set; } } public class Thesauruslink { public string entry_id { get; set; } public string sense_id { get; set; } } public class Note1 { public string text { get; set; } public string type { get; set; } } public class Subsens { public List<string> definitions { get; set; } public List<Example1> examples { get; set; } public string id { get; set; } public List<Semanticclass1> semanticClasses { get; set; } public List<string> shortDefinitions { get; set; } public List<Synonym1> synonyms { get; set; } public List<Thesauruslink1> thesaurusLinks { get; set; } public List<Domainclass> domainClasses { get; set; } } public class Example1 { public string text { get; set; } } public class Semanticclass1 { public string id { get; set; } public string text { get; set; } } public class Synonym1 { public string language { get; set; } public string text { get; set; } } public class Thesauruslink1 { public string entry_id { get; set; } public string sense_id { get; set; } } public class Domainclass { public string id { get; set; } public string text { get; set; } } public class Domainclass1 { public string id { get; set; } public string text { get; set; } } public class Domain { public string id { get; set; } public string text { get; set; } } public class Variantform { public string text { get; set; } } |
Since we have the POCOs ready, we can now introduce the method in Refit to consume the Oxford Dictionaries API
It is great that Uno Platform team and community has embedded Refit into their templates, I highly recommend it as one of the best API Client libraries, I have a complete tutorial on it, you can check the references at the end of this tutorial for the tutorial link to learn more about Refit.
Open IApiClient interface and add a new method(endpoint) to it, as the below:
1 2 |
[Get("/api/v2/entries/en/{term}")] Task<ApiResponse<RootDictionaryEntry>> GetDictionaryEntry(string term, CancellationToken cancellationToken = default); |
For the purpose of this tutorial, we won’t be consuming every single property from this massive response, we will take only a few of them and see how we can bind them within our Uno Platform app UI.
Now let’s add the settings related to connecting your app to Oxford Dictionaries API in your appsettings.development.json and appsettings.json files:
1 2 3 4 5 6 7 8 9 10 |
{ // some other settings here "ApiClient": { "UseNativeHandler": true, "ApiBaseUrl": "https://od-api-sandbox.oxforddictionaries.com", "AppId": "d9da02ff", "AppKey": "bfab4c1d22e6677ac203325a5961b91e" }, // more settings here } |
I know it is not the most secure place to put the app id and app key, but for the purpose of this tutorial we are trying to make things a little simpler.
A little better approach is in the secrets.json that is provided by the Secret Manager, it is a step up to secure your secrets in the development environment, however for production you would require a more secure solution like the Azure Key Vault.
You can also choose the PasswordVault of Uno Platform, even though it is not supported in some operating systems, but it can still cover you for Windows, Android and iO\S builds of your app.
Refit Configuration on Program.cs
Now let’s navigate to our program.cs file and edit the Refit configuration to include the new base url, and pass global headers for app id and app key:
1 2 3 4 5 6 7 8 9 |
.AddRefitClientWithEndpoint<IApiClient, OnlineDictionarySettings>( context, configure: (clientBuilder, options) => clientBuilder .ConfigureHttpClient(httpClient => { httpClient.BaseAddress = new Uri(options!.ApiBaseUrl!); httpClient.DefaultRequestHeaders.Add("app_id", options!.AppId); httpClient.DefaultRequestHeaders.Add("app_key", options!.AppKey); })) |
This tutorial focuses on helping you learn and understand the new state management and architectural approach that has been developed and introduced by Uno Platform, the MVUX.
Model View Update eXtended
As mentioned previously, the Model is represented by the MainModel class which is defined as a record, to guarantee immutability and inside this record we will have the main properties as State and Feed
These are the key concepts and components of MVUX, since you can use State to bind and manage the state of any control, such as TextBox and have a two-way data binding with it.
Feed is another important feature, where you can bind it with an MVUX FeedView, and using an asynchronous calls you can update the UI with incoming streams to the feed. The feed is similar to the observable in MVVM.
In MVUX, you write less boilerplate and extra UI components to achieve the same implementation as MVVM.
So let’s go ahead and prepare our components to build our dictionary app.
We will add a service, an interface, 2 records, see the below:
Definition
1 2 3 4 5 6 7 8 |
public record Definition { public string Word { get; set; } public string Category { get; set; } public string Definitions { get; set; } public IImmutableList<string> Examples { get; set; } public string Synonyms { get; set; } } |
DefinitionWrapper
1 2 3 4 |
public record DefinitionsWrapper(IImmutableList<Definition> Definitions) { public static DefinitionsWrapper Empty() => new([]); } |
DictionaryInterface
1 2 3 4 |
public interface IDictionaryService { Task<DefinitionsWrapper> Lookup(string term, CancellationToken token = default); } |
DictionaryService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
public class DictionaryService(IApiClient apiClient, ILogger<DictionaryService> logger) : IDictionaryService { public async Task<DefinitionsWrapper> Lookup(string term, CancellationToken token) { if (string.IsNullOrEmpty(term)) { return DefinitionsWrapper.Empty(); } var response = await apiClient.GetDictionaryEntry(term, token); if (response.IsSuccessStatusCode && response.Content is not null) { var definitions = new List<Definition>(); foreach (var result in response.Content.results) { var tempDefinitions = result.lexicalEntries.ConvertAll(x => { var senses = x.entries.Where(o => o.senses is not null).SelectMany(o => o.senses); var definitionsInSenses = senses.Where(o => o.definitions is not null).SelectMany(o => o.definitions).ToList(); var examplesInSenses = senses.Where(o => o.examples is not null).SelectMany(o => o.examples).ToList(); var synonymsInSenses = senses.Where(o => o.synonyms is not null).SelectMany(o => o.synonyms).ToList(); var definition = new Definition { Word = result.word, Category = x.lexicalCategory.text, Definitions = string.Join(",", definitionsInSenses), Examples = examplesInSenses.Select(o => o.text).ToImmutableList(), Synonyms = string.Join(",", synonymsInSenses.Select(o => o.text)) }; return definition; }); definitions.AddRange(tempDefinitions); } return new(definitions.ToImmutableList()); } else if (response.Error is not null) { logger.LogError(response.Error, "An error occurred while looking up the term in dictionary."); throw response.Error; } else { return DefinitionsWrapper.Empty(); } } } |
Let’s zoom in the DictionaryService to explain some stuff:
DicionaryService
We defined 1 method in this service, lookup, it takes the term and cancellation token (since it supports asynchronous operations)
Then we are making a call to our new API via Refit Interface where we pass the term and the cancellation token to Refit and so it will make the call and return to us the response wrapped in a Refit ApiResponse object.
Then we do the needed validation to check if the response was ok.
In order to simplify the dictionary lookup that we have done, we will need to covert the response from the full version to a lighter version that will fit the DefinitionWrapper and Definition Records.
Note that in the cases of no results or error, these are being returned as well from the method and they will be seamlessly handled through dedicated templates provided by the MVUX FeedView, you will see these in a bit.
Now since our service is ready, let’s jump to the next step, which is the MainModel, this represents the Model part of the MVUX.
We will add the call to the lookup method, but it will be done in the MVUX style, so let’s see how that call will be done and explain:
Model
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
using MyDictionaryApp.Services; namespace MyDictionaryApp.Presentation; public partial record MainModel { private readonly IDictionaryService _dictionaryService; private readonly ILogger _logger; public MainModel(IDictionaryService dictionaryService, ILogger<MainModel> logger) { _dictionaryService = dictionaryService; _logger = logger; Title = "This is my Dictionary App"; } public string? Title { get; } public IState<string?> SearchTerm => State<string?>.Empty(this); public IListFeed<Definition> SearchDefinitions => SearchTerm .Where(searchTerm => { if (searchTerm is { Length: > 1 }) { _logger.LogInformation($"Search term > 1"); return true; } return false; }) .SelectAsync( async (searchTerm, ct) => { var dictionaryResult = await _dictionaryService.Lookup(searchTerm ?? "", ct); _logger.LogInformation($"After lookup API call, result is {dictionaryResult.Definitions.Select(o => o.Definitions)}"); return dictionaryResult.Definitions; }).AsListFeed(); } |
View
In our tutorial, the view part is the XAML markup, it shows how the page’s or screen’s building blocks can be defined together to render a screen with components that are ready to be data bound.
Let’s take a look at the XAML of the main page design:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
<Page x:Class="MyDictionaryApp.Presentation.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:MyDictionaryApp.Presentation" xmlns:uen="using:Uno.Extensions.Navigation.UI" xmlns:utu="using:Uno.Toolkit.UI" xmlns:um="using:Uno.Material" xmlns:mvux="using:Uno.Extensions.Reactive.UI" NavigationCacheMode="Required" Background="{ThemeResource BackgroundBrush}"> <ScrollViewer> <Grid utu:SafeArea.Insets="VisibleBounds" HorizontalAlignment="Stretch"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="auto"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="auto" /> <RowDefinition Height="auto" /> <RowDefinition Height="auto" /> </Grid.RowDefinitions> <utu:NavigationBar Grid.Row="0" Grid.Column="0" HorizontalAlignment="Center" Content="{Binding Title}" /> <StackPanel Padding="5" Margin="5" Grid.Column="0" Grid.Row="1" Orientation="Vertical" HorizontalAlignment="Stretch"> <TextBox HorizontalAlignment="Stretch" Text="{Binding SearchTerm, Mode=TwoWay}" PlaceholderText="Enter the word to search for" /> <Button HorizontalAlignment="Stretch" Content="Search" Margin="0 10 0 0" Command="{Binding SearchDefinitions}"/> </StackPanel> <mvux:FeedView Grid.Column="0" Grid.Row="2" Source="{Binding SearchDefinitions}" x:Name="DefinitionsFeedView" Padding="3"> <mvux:FeedView.ValueTemplate> <DataTemplate> <ListView ItemsSource="{Binding Data}"> <ListView.ItemTemplate> <DataTemplate> <StackPanel Orientation="Vertical" Margin="5"> <TextBlock FontSize="18" FontWeight="Bold" Text="{Binding Word}"/> <TextBlock FontStyle="Italic" Text="{Binding Category}"/> <TextBlock Text="{Binding Definitions}" HorizontalAlignment="Stretch" /> <TextBlock><LineBreak/></TextBlock> <TextBlock FontSize="18" Text="Synonyms:"></TextBlock> <TextBlock Text="{Binding Synonyms}" HorizontalAlignment="Stretch" /> <TextBlock><LineBreak/></TextBlock> <TextBlock FontWeight="Bold" Text="Examples:"/> <StackPanel Orientation="Vertical"> <ItemsView ItemsSource="{Binding Examples}" > <ItemsView.Layout> <StackLayout></StackLayout> </ItemsView.Layout> </ItemsView> </StackPanel> </StackPanel> </DataTemplate> </ListView.ItemTemplate> </ListView> </DataTemplate> </mvux:FeedView.ValueTemplate> <mvux:FeedView.ProgressTemplate> <DataTemplate> <ProgressRing /> </DataTemplate> </mvux:FeedView.ProgressTemplate> <mvux:FeedView.ErrorTemplate> <DataTemplate> <TextBlock HorizontalAlignment="Center" FontSize="18" Text="Unable to lookup in the dictionary" /> </DataTemplate> </mvux:FeedView.ErrorTemplate> <mvux:FeedView.NoneTemplate> <DataTemplate> <TextBlock HorizontalAlignment="Center" FontSize="18" Text="No Results Found " /> </DataTemplate> </mvux:FeedView.NoneTemplate> </mvux:FeedView> </Grid> </ScrollViewer> </Page> |
See how the MVUX FeedView is abstracting lots of extra boilerplate that would usually be added when working with MVVM like updating the UI with result based on API response, where this response might not be success and return as a bad request due to validation error. Or maybe the resource is not found.
Update
The event that takes place after a user interacts with the UI, either by entering text into the TextBlock, pressing on button, navigating to another screen and so on, as well the update can also be induced by background service like notification or any update from the hardware itself.
1 2 |
<TextBox Width="500" Text="{Binding SearchTerm, Mode=TwoWay}" PlaceholderText="Enter the word to search for" /> |
eXtended
As mentioned earlier in this tutorial, we are relying on the IState and IFeed abstractions to keep updating the FeedView with data or other responses in case some errors happened on the UI or the model type level.
Testing
Testing the app on Windows Desktop
When the dictionary API returns a bad request or other problem, such as 429 Too many requests:
it will display the mvux:FeedView.ErrorTemplate
Testing the App on Android
Here the emulator is running on Pixel 5 – API 34 (Android 14):
Final Thoughts
I think this is one of the best platforms I’ve ever had the chance to learn and work on in .NET
The massive number of features and capabilities that Uno Platform offers is spectacular.
I really like how they extended the MVU approach with great abstractions to help simplify the process of state management and data binding.
Despite the fact that Uno Platform has amazing and brilliant features, I found that MVUX is my favourite feature alongside the Hot Reload.
And above all, it is free and open-source under the Apache 2.0 License.
Uno Platform has been receiving many updates recently to get more support for the features to run better on the different Operating Systems and environments.
References
You can find the complete source code of this tutorial in my GitHub Account.
Follow the link to the official source to Get started learning Uno Platform.
Check the official docs to learn more about MVUX
Xamarin.Forms Migration to Uno Platform Complete Guide
Watch the videos in Uno Platform’s YouTube Channel, lots of great technical sessions and deep dives.
Check my tutorial here to learn about Refit
Bonus
Enjoy the brilliant tunes of the French Baroque Composer:
François Couperin
By Musica ad Rhenum, Jed Wentz conductor
Couperin: Complete Chamber Music (Full Album)
One Comment on “Build a Dictionary App using Uno Platform”