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
                });
      }
}

Introducing TinySvgHelper

For a pretty long time I have had code that I have used for a couple of different apps that uses SkiaSharp to convert an svg image to a Xamarin.Forms ImageSource. I also blogged about it two years ago, https://danielhindrikes.se/index.php/2018/03/13/use-skiasharp-to-convert-svg-to-xamarin-forms-imagesource/. But I never released it as a library until now.

Get started with TinySvgHelper

The library is published to NuGet, https://www.nuget.org/packages/TinySvgHelper/

To install it, search for TinySvgHelper in the **Nuget Package Manager** or install it with the following command:

Install-Package TinySvgHelper

To use it you must add SvgHelper.Init() to your MainActivity and/or AppDelegate.

MainActivity

protected override void OnCreate(Bundle savedInstanceState)
{
    ....
 
    SvgHelper.Init();
 
    ...
}

AppDelegate

public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
    ...
 
    SvgHelper.Init();
 
    ...
}

Use TinySvgHelper

You can use TinySvgHelper either from your XAML- or C# code. For iOS add the svg files to the **Resources** folder and for Android add the svg files to the **Assets** folder.

XAML

First you need to import the namespace:

xmlns:svg="clr-namespace:TinySvgHelper;assembly=TinySvgHelper"

Then you will use it as a markup extension:

<Image Source="{svg:Svg FileName='logo.svg', Height=200,Width=200}" />

You can also specify a color to it:

<ShellContent Icon="{svg:Svg FileName='info.svg', Height=22,Width=22,Color=Black}" Title="Start">
    <views:StartView />
</ShellContent>
```
 
### C#
<pre lang="csharp">
using TinySvgHelper;
var image = new Image();
image.Source = SvgHelper.GetAsImageSource("info.svg", 50, 50, Color.Blue);

Read more

You can find the full documentation, the code and a sample project on GitHub, https://github.com/TinyStuff/TinySvgHelper

Unit testing of code that uses IMemoryCache

I am right now working with an ASP.NET Core 3.1 API where I wanted to use memory caching. The recommended way of that seems to be to use IMemoryCache. Because I am working test-driven I also want to write unit tests and then I, of course, want to test the parts of the code that are using IMemoryCache as well.

My first approach to that was to create a mock of the interface using Moq (our choice of mock framework in this project), but while for example if I use IMemoryCache wit the CacheExtensions (Microsoft.Extensions.Caching.Memory) that is not possible.

My second approach was to use the "real" implementation of it, Microsoft.Extensions.Caching.Memory.MemoryCache, because it was that implementation that was resolved when .NET Core resolved it. Make sure that you have added Microsoft.Extensions.Caching.Memory to your test project, I forgot that. Thanks to Erik Juhlin that you told me on Twitter.

var cacheOptions = Options.Create<MemoryCacheOptions>(new MemoryCacheOptions());
var cache = new MemoryCache(cacheOptions);

So my third approach and also a solution that works are to create your own implementation of IMemoryCache.

Before I created the IMemoryCache implementation I needed to create an implementation of ICacheEntry, which will represent an item in the cache. I decided that the AbsoluteExpirationRelativeToNow- and SlidingExpiration properties should update the AbsoluteExpiration. I am not sure if the "real" implementations is like that, but I got the same behavior in an easy way. Below can you see the complete mock-implementation of ICacheEntry:

public class CacheEntryMock : ICacheEntry
{
        private readonly object key;
 
        public CacheEntryMock(object key)
        {
            this.key = key;
        }
 
        public object Key => key;
 
        public object Value { get; set; }
        public DateTimeOffset? AbsoluteExpiration { get; set; }
 
        private TimeSpan? absoluteExpirationRelativeToNow;
        public TimeSpan? AbsoluteExpirationRelativeToNow
        {
            get => absoluteExpirationRelativeToNow;
            set
            {
                absoluteExpirationRelativeToNow = value;
 
                if (value.HasValue)
                {
                    AbsoluteExpiration = DateTimeOffset.Now.Add(value.Value); 
                }
                else
                {
                    AbsoluteExpiration = null;
                }
            }
}

When I had implemented ICacheEntry I continued with the IMemoryCache interface. I just used a simple Dictionary to store the entries in.
In the TryGetValues method I check if the time for AbsoluteExpiration has passed if so I return true and sets value parameter to null. If the SlidingExpiration property is set I also update the value for AbsoluteExpiration with a new "period" based on the value of the SlidingExpiration property. Here is the complete mock-implementation of IMemoryCache:

public class MemoryCacheMock : IMemoryCache
{
        private IDictionary<object, ICacheEntry> cache = new Dictionary<object, ICacheEntry>();
 
        public ICacheEntry CreateEntry(object key)
        {
            var entry = new CacheEntryMock(key);
 
            if(cache.ContainsKey(key))
            {
                cache[key] = entry;
            }
            else
            {
                cache.Add(key, entry);
            }
 
            return entry;
        }
 
        public void Dispose()
        {
 
        }
 
        public void Remove(object key)
        {
            cache.Remove(key);
        }
 
        public bool TryGetValue(object key, out object value)
        {
            if(!cache.ContainsKey(key))
            {
                value = null;
                return false;
            }
 
            var entry = cache[key];
 
            if(entry.AbsoluteExpiration.HasValue && entry.AbsoluteExpiration > DateTimeOffset.Now)
            {
                value = null;
                return false;
            }
 
            if(entry.SlidingExpiration.HasValue)
            {
                entry.AbsoluteExpiration = DateTimeOffset.Now.Add(entry.SlidingExpiration.Value);
            }
 
            value = entry.Value;
 
            return true;
        }
}

With this code written I was ready to start to write unit tests for my code!

Application Insights for Xamarin- and UWP apps with TinyInsights

About two and a half years ago I created a library with the name TinyInsights with the idea to abstract away the underlying provider for diagnostics and analytics. The reason was that I wanted to make it possible to change the provider without that it affected my code more than in the initial setup of the app. Because diagnostics- and analytics services have a tendency to come and go, for example, Xamarin Insights and HockeyApp are no longer with us. Instead, we now have AppCenter, which also was my first provider for TinyInsights. I also wanted to make it possible to use multiple providers at the same time, because I have worked with apps where business analysts want the data to be in Google Analytics and developers want another service because other services are better for finding errors in apps.

My default provider has been AppCenter the last years, but recently I have missed a couple of important features and you can not analyze the data any deeper. You can export the data to Application Insights and then you can create deeper reports etc. But the way AppCenter store data make some questions harder to make in a good way. For example, if you want to track page views, you have to do that as an event. Application Insights is not built to handle page views that way, because it has real support for page views. AppCenter also lacks the future of tracking dependencies.

So what I decided to do was to create a new provider to TinyInsights with support for Application Insights. But I still using AppCenter for crashes and errors, because it also supporting me to upload symbols to it and Application Insights have no good way to distinguish between an error and a crash. But when I am logging crashes to Application Insights I am setting a custom property with the name "IsCrash" to "true".

I have implemented TrackDependency to all providers, but for AppCenter it only saves all data as custom properties and there is no good way to visualize them, but the data is there if you want to export it to your own visualizer.

Below I have written a short introduction about how to get started with the ApplicationInsightsProvider and TinyInsights in general.

Initializing Application Insights Provider

When initializing TinyInsights you need to specify what provider or providers you want to use. For Application Insights, you only need to give it the IntrumentationKey. If you don't pass different keys for different platforms all data will be collected together and not as in AppCenter where you create one app per platform.

var appInsights = new ApplicationInsightsProvider("{InstrumentationKey}");
var appCenter = new AppCenterProvider("{keyForIOS}", "{keyForAndroid}", {keyForUWP});
appCenter.IsTrackPageViewsEnabled = false;
appCenter.IsTrackEventsEnabled = false;
appCenter.IsTrackDependencyEnabled = false;
 
TinyInsights.Configure(appCenterProvider, applicationInsightsProvider);

Handling exceptions

To track exceptions/errors, just use the TrackErrorAsync method. Crashes will automatcially be tracked and sent to Application Insights next time the user is starting the app.

catch(Ecception ex)
{
     await TinyInsights.TrackErrorAsync(ex);
}
 
//with properties
var properties = new  Dictionarty<string, string>();
properties.Add("MyFirstProperty", "MyFirstValue");
properties.Add("MySecondProperty", "MySeconndValue");
 
catch(Ecception ex)
{
     await TinyInsights.TrackErrorAsync(ex, properties);
}

Tracking dependencies

There are a two of ways to track dependencies with TinyInsights.
The first and the basic method is TrackDependencyAsync, and is also used in the background by the other way to do it.

var startTime = DateTimeOffset.Now;
 
var success = await GetData();
 
var duration = DateTimeOffset.Now - startTime
 
await TinyInsights.TrackDependencyAsync("api.mydomain.se", "https://api/mydomain.se/v1/data/get", startTime, duration, success);

The second way is to create a TinyDependency object that handles most of the tracking for you. You will do that by just by wrapping your code for the dependency in a using statement.

using (var tracker = TinyInsights.CreateDependencyTracker("api.mydomain.se", "https://api/mydomain.se/v1/data/get"))
{
     await GetData();
}

If the dependency succeded that is fine, but if it not you need to handle that on your own, using the Finish method of the TinyDependency object.

using (var tracker = TinyInsights.CreateDependencyTracker("api.mydomain.se", "https://api/mydomain.se/v1/data/get"))
{
     try
     {
          var repsonse = await GetData();
 
          if(!response.IsSuccessStatusCode)
          {
               await tracker.Finish(false, (int)response.StatusCode);
          }
     }
     catch(Exception ex)
     {
          tracker.Finish(false, ex);
     }
}

Tracking page views

To track page views, just use the TrackPageViewAsync method.

await TinyInsights.TrackPageViewAsync("SuperCoolView");
 
//with properties
var properties = new  Dictionarty<string, string>();
properties.Add("MyFirstProperty", "MyFirstValue");
properties.Add("MySecondProperty", "MySeconndValue");
 
await TinyInsights.TrackPageViewAsync("SuperCoolView", properties);

Tracking events

To track events, just use the TrackEventAync method.

await TinyInsights.TrackEventAsync("SuperCoolEvent");
 
//with properties
var properties = new  Dictionarty<string, string>();
properties.Add("MyFirstProperty", "MyFirstValue");
properties.Add("MySecondProperty", "MySeconndValue");
 
await TinyInsights.TrackEventAsync("SuperCoolEvent", properties);

Feedback

If you have any feedback, please create an issue on GitHub. You will also be welcome to contribute to the library with improvments or/and other providers.
 

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.