Build a Dictionary App using Uno Platform

Aram Tchekrekjian
19 min readAug 22, 2024

--

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:

dotnet tool install -g uno.check

Then continue with running uno-check tool:

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:

namespace MyDictionaryApp.Models;
public class RootDictionaryEntry
{
public string id { get; set; }
public Metadata metadata { get; set; }
public List 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 lexicalEntries { get; set; }
public string type { get; set; }
public string word { get; set; }
}
public class Lexicalentry
{
public List derivatives { get; set; }
public List 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 etymologies { get; set; }
public List pronunciations { get; set; }
public List senses { get; set; }
}
public class Pronunciation
{
public string audioFile { get; set; }
public List dialects { get; set; }
public string phoneticNotation { get; set; }
public string phoneticSpelling { get; set; }
}
public class Sens
{
public List constructions { get; set; }
public List definitions { get; set; }
public List examples { get; set; }
public string id { get; set; }
public List semanticClasses { get; set; }
public List shortDefinitions { get; set; }
public List synonyms { get; set; }
public List thesaurusLinks { get; set; }
public List notes { get; set; }
public List subsenses { get; set; }
public List domainClasses { get; set; }
public List domains { get; set; }
public List variantForms { get; set; }
}
public class Construction
{
public string text { get; set; }
}
public class Example
{
public string text { get; set; }
public List 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 definitions { get; set; }
public List examples { get; set; }
public string id { get; set; }
public List semanticClasses { get; set; }
public List shortDefinitions { get; set; }
public List synonyms { get; set; }
public List thesaurusLinks { get; set; }
public List 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:

[Get("/api/v2/entries/en/{term}")]
Task> 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:

{
// 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:

.AddRefitClientWithEndpoint(
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

public record Definition
{
public string Word { get; set; }
public string Category { get; set; }
public string Definitions { get; set; }
public IImmutableList Examples { get; set; }
public string Synonyms { get; set; }
}

DefinitionWrapper

public record DefinitionsWrapper(IImmutableList Definitions)
{
public static DefinitionsWrapper Empty() => new([]);
}

DictionaryInterface

public interface IDictionaryService
{
Task Lookup(string term, CancellationToken token = default);
}

DictionaryService

public class DictionaryService(IApiClient apiClient, ILogger logger) : IDictionaryService
{
    public async Task 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();
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

using MyDictionaryApp.Services;
namespace MyDictionaryApp.Presentation;public partial record MainModel
{
private readonly IDictionaryService _dictionaryService;
private readonly ILogger _logger;
public MainModel(IDictionaryService dictionaryService, ILogger logger)
{
_dictionaryService = dictionaryService;
_logger = logger;
Title = "This is my Dictionary App";
}
public string? Title { get; }
public IState SearchTerm => State.Empty(this); public IListFeed 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:

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.

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)

--

--

Aram Tchekrekjian
Aram Tchekrekjian

Written by Aram Tchekrekjian

Microsoft MVP | Technical Manager at Aramex | Founder of Codingsonata.com | C#, ASP.NET Core, Android, Angular

No responses yet