Get started with TinyMvvm

This is a tutorial that will guide you through how to get started building an app using Xamarin.Forms and TinyMvvm.

  1. Create a new project based on the template for a blank Xamarin.Forms app. In this example, the name of the project will be SampleApp. The template will generate one project for shared code and one project per target platform.

  2. Install the following NuGet packages into all projects:

    • TinyMvvm.Forms
    • TinyMvvm.Autofac
  3. Create a new ContentPage XAML in the SampleApp project and give it the name AppShell. And add the following content to it.

    <Shell xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:views="clr-namespace:SampleApp.Views" x:Class="SampleApp.AppShell">
        <TabBar>
            <ShellContent Title="Home" Route="home" >
                <views:MainView />
             </ShellContent>
            <ShellContent Title="About" Route="about">
                <views:AboutView />
             </ShellContent>
        </TabBar>
    </Shell>
  4. Remove the base class in the AppShell.xaml.cs so it will look like this:

    public partial class AppShell
    {
        public AppShell()
        {
            InitializeComponent();
        }
    }
  5. Navigate to App.xaml.cs and set MainPage to AppShell. You can also delete the MainPage.xaml- and MainPage.xaml.cs files.

    public partial class App : Application
    {
        public App()
        {
            InitializeComponent();
    
            MainPage = new AppShell();
        }
    }
  6. On the rows before you set MainPage to AppShell write the initialization code for TinyMvvm:

    • Use ShellNavigationHelper and register all views in the current Assembly. This register all views so we can use the class name as the key if we use the classic (non-Shell) navigation or we can use ViewModelNavigation that is powered by the Shell navigation.
    • The sample is using Autofac as it's IoC container, but you can use whatever container you want. The only thing you need to do to use another is to create an implementation of IResolver that uses it. Register all classes that is a subType of Page (Xamarin.Forms) and ViewModelBase (TinyMvvm).
    • Register the container to the Resolver, the Resolver is used internally by TinyMvvm, but you can also use it in your code.
    • The last thing to do is to call the *Initialize method for TinyMvvm.

      public App()
      {
          InitializeComponent();
      
          var navigationHelper = new ShellNavigationHelper();
      
          var currentAssembly = Assembly.GetExecutingAssembly();
          navigationHelper.RegisterViewsInAssembly(currentAssembly);
      
          var containerBuilder = new ContainerBuilder();
      
          containerBuilder.RegisterInstance<INavigationHelper>(navigationHelper);
      
          var appAssembly = typeof(App).GetTypeInfo().Assembly;
          containerBuilder.RegisterAssemblyTypes(appAssembly)
                 .Where(x => x.IsSubclassOf(typeof(Page)));
      
          containerBuilder.RegisterAssemblyTypes(appAssembly)
                 .Where(x => x.IsSubclassOf(typeof(ViewModelBase)));
      
          var container = containerBuilder.Build();
      
          Resolver.SetResolver(new AutofacResolver(container));
      
          TinyMvvm.Forms.TinyMvvm.Initialize();
      
          MainPage = new AppShell();
      }
  7. Create a new folder called ViewModels in the SampleApp project.

  8. Create two classes in the ViewModels folder, MainViewModel and AboutViewModel.

  9. Add ViewModelBase as the base class for MainViewModel and AboutViewModel:

    public class MainViewModel : ViewModelBase
    {
        public MainViewModel()
        {
        }
    }
    public class AboutViewModel : ViewModelBase
    {
        public AboutViewModel()
        {
        }
    }
  10. Edit MainView.xaml and AboutView.xaml to have ViewBase as it's base class. To set BindingContext to the the ViewModel, use the x:TypeArguments for the View.

    <mvvm:ViewBase
    xmlns:mvvm="clr-namespace:TinyMvvm.Forms;assembly=TinyMvvm.Forms"
    xmlns:vm="clr-namespace:SampleApp.ViewModels"
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="SampleApp.Views.MainView" 
    x:TypeArguments="vm:MainViewModel">
    
    </mvvm:ViewBase>
    <mvvm:ViewBase
    xmlns:mvvm="clr-namespace:TinyMvvm.Forms;assembly=TinyMvvm.Forms"
    xmlns:vm="clr-namespace:SampleApp.ViewModels"
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="SampleApp.Views.AboutView" 
    x:TypeArguments="vm:AboutViewModel">
    
    </mvvm:ViewBase>
  11. Remove the base class from MainView.xaml.cs and AboutView.xaml.cs. Because is it a partial class you don't have to specify it both in the XAML-file and in the code-behind file.

  12. Create a new View, DetailsView, and a new ViewModel, DetailsViewModel as in the same style as above.

  13. In MainViewModel, add the following below. The override of Initialize is running when BindingContext for the view has been set. You can also override OnAppearing to run code when the view will appear and OnDisappearing to run code when the view will disappear. IsBusy can be used to bind an ActivityIndicator to. For Commands, use TinyCommand instead of Xamarin.Forms.Command to keep the ViewModel clean from Xamarin.Forms references. To navigate, use the Navigation property from ViewModelBase. Here you can use the name of a ViewModel as a part of a URL to navigate to a route. With TinyMvvm you can also specify a parameter to pass to the target ViewModel.

    public class MainViewModel : ViewModelBase
    {
        private ObservableCollection<string> names;
        public ObservableCollection<string> Names
        {
            get => names;
            set => Set(ref names, value);
        }
    
        public async override Task Initialize()
        {
            IsBusy = true;            
            await base.Initialize();
    
            Names = new ObservableCollection<string>(new List<string>()
            {
                "Daniel",
                "Ella",
                "Willner"
            });
    
            IsBusy = false;
        }
    
        public override Task OnAppearing()
        {
            return base.OnAppearing();
        }
    
        public override Task OnDisappearing()
        {
            return base.OnDisappearing();
        }
    
        private ICommand details;
        private ICommand Details => details ??= new TinyCommand<string>(async(name) =>
        {
            await Navigation.NavigateToAsync($"{nameof(DetailsViewModel)}?name={name}", DateTimeOffset.Now);
        });
    }
  14. To MainView.xaml add the following code to show the bind a CollectionView to the data in the ViewModel:

     <Grid>
        <ActivityIndicator HorizontalOptions="Center" VerticalOptions="Center" IsRunning="False" IsVisible="{Binding IsBusy}" />
        <CollectionView x:Name="Names" ItemsSource="{Binding Names}" IsVisible="{Binding IsNotBusy}">
            <CollectionView.ItemTemplate>
                <DataTemplate>
                    <ContentView Padding=" 10">
                        <ContentView.GestureRecognizers>
                            <TapGestureRecognizer Command="{Binding Source={x:Reference Names}, Path=BindingContext.Details}" CommandParameter="{Binding}" />
                        </ContentView.GestureRecognizers>
                        <Label Text="{Binding }" />
                    </ContentView>
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
    </Grid>
  15. Go to DetailsViewModel.cs and add the code below to receive the parameters sent to it. QueryParameters will be a Dictionary<string, string> and contains the query parameters specified in the navigation URL. If you also specified a parameter you can access it via the NavigationParameter property.

    
    public class DetailsViewModel : ViewModelBase
    {
        public async override Task Initialize()
        {
            await base.Initialize();
    
            Name = QueryParameters["name"];
            var dateParameter = (DateTimeOffset)NavigationParameter;
    
            Date = dateParameter.ToString();
        }
    
        private string name;
        public string Name
        {
            get => name;
            set => Set(ref name, value);
        }
    
        private string date;
        public string Date
        {
            get => date;
            set => Set(ref date, value);
        }
    }
  16. In the DetailsView.cs add the following code to show the data passed from MainViewModel:

    <mvvm:ViewBase
        xmlns:mvvm="clr-namespace:TinyMvvm.Forms;assembly=TinyMvvm.Forms"
        xmlns:vm="clr-namespace:SampleApp.ViewModels"
        xmlns="http://xamarin.com/schemas/2014/forms"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        x:Class="SampleApp.Views.DetailsView" x:TypeArguments="vm:DetailsViewModel">
        <StackLayout Padding="10">
            <Label Text="{Binding Name}" />
            <Label Text="{Binding Date}" />
        </StackLayout>
    </mvvm:ViewBase>
  17. Navigate to AboutViewModel.cs and add the following code to use with a button to navigate to the MainView using the route specified in the Shell.

    public class AboutViewModel : ViewModelBase
    {
        private ICommand home;
        public ICommand Home => home ?? new TinyCommand(async () =>
        {
            await Navigation.NavigateToAsync("//home");
        });
    }
  18. Go to AboutView.xaml and create a Button to use with the Command in AboutViewModel.

    <mvvm:ViewBase
        xmlns:mvvm="clr-namespace:TinyMvvm.Forms;assembly=TinyMvvm.Forms"
        xmlns:vm="clr-namespace:SampleApp.ViewModels"
        xmlns="http://xamarin.com/schemas/2014/forms"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        x:Class="SampleApp.Views.AboutView" x:TypeArguments="vm:AboutViewModel">
     <Grid>
         <Button Text="Go home!" Command="{Binding Home}" />
     </Grid>
    </mvvm:ViewBase>

The complete code for this sample can be found here: https://github.com/TinyStuff/TinyMvvm/tree/master/src/Samples

TinyMvvm 2.2 – Shell Navigation Updates

Today I released TinyMvvm 2.2 (2.2.2).

In this version I have added support for passing parameters when navigating with ShellNavigationHandler. You can now use both query parameters and passing a object as a parameter.

So in your ViewModel with ViewModelBase as the base class you can use it like this:

var persons = new List<string>()
{
   "Daniel",
   "Johan"
};

Navigation.NavigateToAsync($"{nameof(ContactViewModel)}?id=1", persons);

You can also use the NavigationHelper class to navigate.

NavigationHelper.Current.NavigateToAsync($"{nameof(ContactViewModel)}?id=1", persons);

In the receiving ViewModel, you can access the parameters via the QueryParameters- and NavigationParameter properties.

public class ContactViewModel : ViewModelBase
{
    public async override Task Initialize()
    {
        await base.Initialize();

        var id = (int)QueryParameters["id"];
        var persons = (List<string>)NavigationParameter;
    }
}

Read more

If you want to read more about TinyMvvm, I can recommend theese blog posts:
TinyMvvm 2.1.1 – Introducing ViewModel navigation
Introducing TinyMvvm 2.0
TinyMvvm – The best MVVM library for Xamarin.Forms!?

Contribute

The full source code and documentation can be found on GitHub. Feedback and contribtions are very welcome.

TinyMvvm 2.1.1 – Introducing ViewModel navigation

Today I released TinyMvvm 2.1.1. The release contains the following:

  • Updated Xamarin.Forms dependency so it has dependency on the lastest stable version of Xamarin.Forms.
  • ViewModel navigation

ViewModel navigation

The idea about ViewModel navigation is from Shane Neuville from the Xamarin.Forms team, he reviewed TInyMvvm in one of his live streams on Twitch. During the stream he walktrough the code and he also wrote code for ViewModel Navigation. After the stream he sent me the code. For the 2.1.1 release I have added his code to the repopsitory. Thank you Shane for contributing to TinyMvvm!

To use ViewModel Navigation you need to register view by running the RegisterViewInAssembly method and pass the Assembly where your views are located. ViewModel navigation can only be used with the ShellNavigationHelper and for Views that has ViewBase<TViewModel> as its base class.

var navigationHelper = new ShellNavigationHelper();
navigationHelper.RegisterViewsInAssembly(Assembly.GetExecutingAssembly());

Then you can navigate like this in your ViewModel to navigate to for example the AboutView with that uses AboutViewModel:

await Navigation.NavigateToAsync(nameof(AboutViewModel));
//or with query parameter,
await Navigation.NavigateToAsync($"{nameof(AboutViewModel)}?id=1");

The pros by using this is that you don't need to handle keys in your ViewModel and you don't either need to reference the views to use nameof(AboutView) as the key.

Read more

If you don't have used TinyMvvm before I recomend you to read this blog post.

Introducing TinyMvvm 2.0

TinyMvvm is a library that I created a couple of years ago. The idea was to build a small (tiny) MVVM library that made me more productive. I first released TinyNavigationHelper that helped med to abstract the Xamarin.Forms navigation, because I do not want my ViewModels to have references to Xamarin.Forms.

Last time I updated TinyMvvm was about one and a half year ago, the reason I have not updated it since then is that it has been stable and worked very well for me in multiple apps I have worked with for multiple clients.

When Xamarin.Forms Shell was released I started to work with some improvements to make TinyMvvm work with Xamarin.Forms Shell. But I never finished them, because the current version worked very well together with Shell. The only thing not supported was URL navigation.

But in the current app, I work with I had a use case where URL navigation would be very nice to use. Of course, I could have used it without using the navigation in TinyMvmm. But there are some great features of TinyMvvm that I missed then. For example to get navigation parameters in the ViewModel without having to pass them manually from the view and if I wanted to navigate from the ViewModel I had to add references to Xamarin.Forms inside of them.

New features

There is a couple of new stuff added to TinyMvvm in this release.

ShellNavigationHelper

If you want to use URL navigation together with TinyMvvm you have to use the ShellNavigtionHelper. ShellNavigationHelper is a subclass of FormsNavigationHelper.

var navigationHelper = new TinyNavigationHelper.Forms.ShellNavigationHelper();
navigationHelper.RegisterViewsInAssembly(appAssembly);
navigationHelper.RegisterView("MainView", typeof(MainView));

The method that is supporting URL navigation is the NavigateTo(string key) method. All other methods of INavigationHelper will use traditional Xamarin.Forms NavigationService. To NavigateTo you can either specify a URL or a key to a view. If it got a key that you have registered as in the code about it will use the traditional NavigationService, otherwise, it will use Shell navigation with URL.

await NavigationHelper.Current.NavigateTo("//home/messages?id=1");

If you are in a ViewModel with ViewModelBase as parent you can use the Navigation property of of the ViewModelBase,

await Navigation.NavigateTo("//home/messages?id=1");

QueryParameters in ViewModelBase

With URL navigation you can pass query parameters. With TinyMvvm you can access them from the ViewModel via the QueryParameters property of ViewModelBase. QueryParameters is of type Dictionary<string, string>.

public override async Task Initialize()
{
    await base.Initialize();

    var id = QueryParameters["id"];
}

Optimization

In TinyMvvm 1.x there was a dependency on TinyNavigationHelper, in 2.0 is TinyNavigationHelper added as a submodule and compiled into the same assembly as TinyMvvm. This reduces the amount of referenced assemblies that Xamarin.Forms have to search for custom renderers in.

Breaking changes

The constructor to FormsNavigationHelper has changed. You can not pass an instance of Xamarin.Forms.Application anymore, the reason is that it not is necessary to provide the navigation helper with it.

Obsolete fetures

The BeginInvokeOnMainThread future of ViewModelBase has been marked as obsolete, the recommendation is to use Xamarin.Essentials.MainThread.BeginInvokeOnMainThread instead.

TinyMvvm basics

Here are some of the basics of TinyMvvm, you will find the full docs and a sample project here, https://github.com/TinyStuff/TinyMvvm.

Initiation

TinyMvvm.Forms.TinyMvvm.Initialize();

ViewBase<T>

Features (or drawbacks) of the ViewBase

  • It creates the ViewModel for you if you inherit the ViewModel from ViewModelBase

The view should inherit from ViewBase<T> where T is the ViewModel. The ViewModel can be any class that has ViewModelBase as the base class.

ViewBase<T> itself inherits from Xamarin.Forms.ContentPage and can be treated by Xamarin Forms as any page.

If you decide to use ViewModelBase as the base class for your view model and at the same time have the IoC resolver enabled, the view will automatically create the view model for you when the view is created. Hence no need to inject the view model in the constructor and assign it manually. Feature or not, you decide.

An example of a typical page in TinyMvvm would look like this:

<tinymvvm:ViewBase x:TypeArguments="viewmodels:MainViewModel" 
    xmlns="http://xamarin.com/schemas/2014/forms" 
    xmlns:viewmodels="clr-namespace:TinyMvvm.Forms.Sample.ViewModels;assembly=TinyMvvm.Forms.Samples"
    xmlns:tinymvvm="clr-namespace:TinyMvvm.Forms;assembly=TinyMvvm.Forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
    x:Class="TinyMvvm.Forms.Sample.Views.MainView">

</tinymvvm:ViewBase>

What you need to do is:

  • Define two namespaces (viewmodels and tinymvvm)
  • Change the view base type to tinymvvm:ViewBase
  • Add a type argument pointing to your ViewModel

### ViewModelBase

TinyMvvm also defines a base class for the view model called `ViewModelBase`.

Features of the ViewModelBase

* Wraps navigation for you through the INavigation interface (Implemented in TinyNavigation)
* Implements INotifyPropertyChanged for you
* Propagates life cycle events to the view (Initialize, OnAppearing, OnDisapparing)

### IoC
Tiny Mvvm is not bound to any specific IoC provider. There is a provider for Autofac that you can install with the "TinyMvvm.Autofac" package.

Install-Package TinyMvvm.Autofac

TinyMvvm has a Resolver in its core project. To use it you need to add on a provider to it that implements the IResolver interface, for example, our Autofac provider.

```csharp
var container = builder.Build();
var resolver = new AutofacResolver(container);
Resolver.SetResolver(resolver);
var navigationHelper = Resolver.Resolve();

TinyCommand

TinyMvvm has its own implementation of ICommand that not has any references to Xamarin.Forms so you can use it in a library without reference Xamarin.Forms.

public ICommand WithParameter
{
      get
      {
                return new TinyCommand(async() =>
                {
                   //Do stuff
                });
      }
}

Add a swipe menu to your Xamarin.Forms app

A common pattern in mobile apps is to use a swipe menu, a menu that appears when a user is swiping an item in a list for example. We often see this in mail- and messages apps.

For Xamairn.Forms ListView we had ContextActions. but they were pretty limited. But in the upcoming 4.4.0 release of Xamarin.Forms (now in public preview) a new control is introduced, SwipeView. SwipeView makes it possible to add a swipe menu to any other item. To do that, just wrap any item in a SwipeView. We can add swipe items in any direction, left, right, top, and bottom.

<SwipeView>
    <SwipeView.LeftItems>
        <SwipeItem BackgroundColor="Orange" Command="{Binding Star}" Text="Star" IconImageSource="{FontImage FontFamily={StaticResource IconFont}, Glyph={x:Static constants:Icons.Star}, Size=22, Color=White}" />
    </SwipeView.LeftItems>
    <SwipeView.RightItems>
         <SwipeItem BackgroundColor="Red" Command="{Binding Delete}" Text="Ta bort" IconImageSource="{FontImage FontFamily={StaticResource IconFont}, Glyph={x:Static constants:Icons.RecycleBin}, Size=22, Color=White}" />
    </SwipeView.RightItems>
 
<!--Content here-->
</SwipeView>

There are two different types of swipe behaviours/modes you can use for swipe, reveal and execute. Reveal shows options that the user has and to execute it, he/she needs to tap it. If mode is set to Execute, it will execute directly on swipe if the swipe gesture is long enough. To set the mode for swipe items we need to wrap the items in a SwipeItems element as below:

<SwipeView.LeftItems>
    <SwipeItems Mode="Reveal">
        <SwipeItem BackgroundColor="Orange" Command="{Binding Star}" Text="Star" IconImageSource="{FontImage FontFamily={StaticResource IconFont}, Glyph={x:Static constants:Icons.Star}, Size=22, Color=White}" />
    </SwipeItems>
</SwipeView.LeftItems>
<SwipeView.RightItems>
     <SwipeItems Mode="Execute">
         <SwipeItem BackgroundColor="Red" Command="{Binding Delete}" Text="Ta bort" IconImageSource="{FontImage FontFamily={StaticResource IconFont}, Glyph={x:Static constants:Icons.RecycleBin}, Size=22, Color=White}" />
     </SwipeItems>
</SwipeView.RightItems>

I have noticed that it is still some bugs in the control and we can also see that if we take a look at the issue list on GitHub, https://github.com/xamarin/Xamarin.Forms/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+swipeview. But I have started to use it anyway, because it was enought stable for my needs. If you found any bug or have feedback I know that the Xamarin.Forms teams is more than happy if you report it to them.

DarkMode and LightMode with MergedDictionaries

iOS 13 introduces Dark Mode, David Ortinau has written a great blog post on the official Xamarin blog about Dark Mode and Xamairn.Forms, https://devblogs.microsoft.com/xamarin/modernizing-ios-apps-dark-mode-xamarin/.

In this post, I will show how we can use MergedDictionaries to work with a base theme that applies both to the dark mode and the light mode.

First, create two new XAML pages and change the base class to ResourceDictionary, name of DarkTheme and the other on LightTheme. In those, we should define all colors that you should use for our application.

DarkTheme:

<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms" 
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                    x:Class="MyApp.DarkTheme">
    <Color x:Key="TextColor">#FFFFFF</Color>
    <Color x:Key="PrimaryBackgroundColor">#333333</Color>
</ResourceDictionary>

LightTheme:

<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms" 
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                    x:Class="MyApp.LightTheme">
    <Color x:Key="TextColor">#333333</Color>
    <Color x:Key="PrimaryBackgroundColor">#FFFFFF</Color>
</ResourceDictionary>

When you have done that we can create a new XAML page and also here we will change the base class to ResourceDictinary. In this resource dictionary, we will define the styles for elements in our application. Here we also can define common colors. Use DymanicResource for properties that will be updated it theme/mode changes. Read more about that in the blog post from David Ortinau.

<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms" 
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
                    x:Class="MyApp.BaseTheme">
 
     <Color x:Key="PrimaryColor">#1e88e5</Color>
     <Color x:Key="PrimaryTextColor">#FFFFFF</Color>
 
    <Style TargetType="Label">
      <Setter Property="TextColor" Value="{DynamicResource TextColor}" />
    </Style>
 
    <Style TargetType="Button">
        <Setter Property="BackgroundColor" Value="{StaticResource PrimaryColor}" />
        <Setter Property="TextColor" Value="{StaticResource PrimaryTextColor}" />
        <Setter Property="FontAttributes" Value="Bold" />
    </Style>
</ResourceDictionary>

Now we will merge this with the resource dictionaries for dark theme and light theme. Import the namespace and add the base theme to both of the themes.

DarkTheme

<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms" 
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                    xmlns:local="clr-namespace:MyApp"
                    x:Class="MyApp.DarkTheme">
    <Color x:Key="TextColor">#FFFFFF</Color>
    <Color x:Key="PrimaryBackgroundColor">#333333</Color>
 
    <ResourceDictionary.MergedDictionaries>
         <local:BaseTheme />
    </ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

LightTheme:

<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms" 
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                    xmlns:local="clr-namespace:MyApp"
                    x:Class="MyApp.LightTheme">
    <Color x:Key="TextColor">#333333</Color>
    <Color x:Key="PrimaryBackgroundColor">#FFFFFF</Color>
 
    <ResourceDictionary.MergedDictionaries>
         <local:BaseTheme />
    </ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

And now you can apply themes like in the official Xamarn blog post.

App In The Cloud – Live Code – Episode 1

Together with my friends and colleagues, Johan Karlsson and Mats Törnberg, we have started to live code on Twitch, https://www.twitch.tv/danielhindrikes.
Right now we have started to build a Xamarin.Forms app from scratch, the plan is to continue to build that app, live on Twitch every Monday at 12:00 CET (Swedish time). If you missed it, we will also publish all episodes on YouTube.

Here is the recording from the stream:

Xamarin.Forms Visual

With Xamarin.Forms 3.6 Xamarin/Microsoft is shipping a new feature, Visual. Visual was earlier a part of the Xamarin.Forms 4.0 release, but now it was included in the Xamarin.Forms 3.6 release.

Material design

Visual was created with the idea to make it easy for developers to implement one common user experience for both iOS and Android. The Xamarin.Forms 3.6 release is delivered with one Visual, "Material". "Material" is a Visual that makes it easier for developers to implement Material Design.

Additional NuGet package

To use Visual with Material Design there is an additional NuGet package we need to install, and it is Xamarin.Forms.Visual.Material.

iOS

What happen when installing Xamarin.Forms.Visual.Material is that we got the renderers for Material Design and also Xamarin.iOS.MaterialComponents is installed. Xamarin.iOS.MaterialComponents contained C# bindings to the library that Google has created for developers that want to use Material Design on iOS.

Android

Because of that we already have Material Desing components on Android we will only get optimized renderers for use with Visual.

To use Visual with Material Design there are some requirements:

  • Target Android Framework needs to be v9.0 or greater.
  • Minimum Android version has to be greater than 5.0 (API 21). Material Design was introduced in 5.0.
  • The MainActivity has to have FormsAppCompatActivity as base class.
  • All Android support layers needs to be of version 28 or greater.

Using Visual

Visual can be used on an individual element, or on page-level. If we set it on page-level it will affect all elements that support the Visual that we selected.

Visual on a Entry:

<Entry Visual="Material" Placeholder="Enter your name" />

Visual on a ContentPage:

<ContentPage Visual="Material" Title="My page with Material Design" ...>
...
</ContentPage>
iOS

Android

Create a Visual

It is pretty easy to create a new Visual, but it can take some time because we have to create renderers for each control and platform. But if we have a design that we want to use cross-platform and for multiple apps, it can be a good idea to package our own Visual.

The first thing we need to do is to create a class that implements the IVisual interface.

public class LeetVisual : IVisual
{
     public LeetVisual()
     {
     }
}

When we have done that we need to create renderers for each element that we want to support our own Visual. The key part to getting it to work is that we will add which Visual(s) the renderer is for in the ExportRendererAttribute. The code below will make all Frames on the iOS app to have green as the BackgroundColor. The code for the rest of the renderers for both iOS and Android can be found in the GitHub repository for this sample application.

[assembly: ExportRenderer(typeof(Xamarin.Forms.Frame), typeof(LeetFrameRenderer), new[] { typeof(VisualDemo.LeetVisual) })]
namespace VisualDemo.iOS.Renderers
{
    public class LeetFrameRenderer : FrameRenderer
    {
        public LeetFrameRenderer()
        {
        }
 
        protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
        {
            base.OnElementChanged(e);
 
            Element.BackgroundColor = Color.FromHex("#80C565");
        }
    }
}

When we have created the Visual and all the renderers we can use our own Visual by specifying the name of it in the UI-Code. We can use only Leet (as named in this sample) or LeetVisual as the value for Material.

<ContentPage 
    xmlns="http://xamarin.com/schemas/2014/forms" 
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
    x:Class="VisualDemo.FriendsPage" Visual="Leet" Title="My Friends">
iOS

Android

Code

The full code for this sample application can be found on GitHub, https://github.com/dhindrik/VisualDemo

Read more

Here are some links if you want to read more about Visual: