mvvmlight

Using Microsoft’s Extensions.DependencyInjection package in (Xamarin.Forms) MVVM applications (Part 2)

Using Microsoft’s Extensions.DependencyInjection package in (Xamarin.Forms) MVVM applications (Part 2)

The Key

Our goal is to add keyed registrations to the IServiceCollection, so we need a common denominator to build upon. As I was able to use a string with the SimpleIoc implementation of MVVMLight for years now, I decided to move on with that and created the following, very complex interface:

public interface IViewModelKey
{
    string Key { get; set; }
}

Every ViewModel that should be registered by Key needs to implement that interface from now on in my MVVM environment.

The Resolver

Back in the MVVMLight times, I was able to query the SimpleIoc registrations with the key I was searching for. In the Microsoft.Extensions.DependencyInjection world, things get a bit more complex. While there are different ways to solve the problem (there are some libraries extending the IServiceProvider with additional methods out there, for example), I decided to use the IServiceProvider itself and go down the resolver interface/implementation road.

Let’s have a look at the interface first:

public interface IViewModelByKeyResolver<T> where T : IViewModelKey
{
    public T GetViewModelByKey(string key);
}

Nothing too special here, just a generic implementation of the resolver interface with the requirement of the IViewModelKey implementation from above. This makes the usage pretty straight forward. The more important part here is the implementation, though. Let’s have a look at mine:

public class ViewModelByKeyResolver<T> : IViewModelByKeyResolver<T> where T : IViewModelKey
{
    private readonly IServiceProvider _serviceProvider;

    public ViewModelByKeyResolver(IServiceProvider serviceProvider)
        => _serviceProvider = serviceProvider;

    public T GetViewModelByKey(string key)
        => _serviceProvider.GetServices<T>().SingleOrDefault(vm => vm.Key == key);
}

The registration of the implementation will automatically inject the IServiceProvider instance at runtime for me here. The GetViewModelByKey method searches all registrations of the given type for the key and returns the desired instance.

Registering the Resolver and keyed ViewModels

The registration of the resolver is done like all the other registrations:

this.ServiceDescriptors.TryAddSingleton<IViewModelByKeyResolver<KeyedViewModel>, ViewModelByKeyResolver<KeyedViewModel>>();

Replace KeyedViewModel with your individual type that implements your key interface. That’s it.

For the registration of the KeyedViewModel instances, there is one thing to pay attention to, though. You cannot use the TryAdd{Lifetime} methods here for registration. Instead, just use the Add{Lifetime} method to register them. Here is a sample:

this.ServiceDescriptors.AddSingleton<KeyedViewModel>(new KeyedViewModel("Key1"));
this.ServiceDescriptors.AddSingleton<KeyedViewModel>(new KeyedViewModel("Key2"));
this.ServiceDescriptors.AddSingleton<KeyedViewModel>(new KeyedViewModel("Key3"));
this.ServiceDescriptors.AddSingleton<KeyedViewModel>(new KeyedViewModel("Key4"));
this.ServiceDescriptors.AddSingleton<KeyedViewModel>(new KeyedViewModel("Key5"));

If you know the keyed ViewModels already at the time of your app startup, you can add them right away and create the IServiceProvider instance as shown in my first post. In most cases, however, you will know the information of the keyed instances only at runtime. Luckily, my Xamarin.Forms implementation already has the solution built in. Here is a short reminder:

public ServiceCollection? ServiceDescriptors { get; private set; }

private IServiceProvider? _services;

public IServiceProvider? Services => _services ??= BuildServiceProvider();

public IServiceProvider? BuildServiceProvider(bool resetExisiting = false)
{
    if (this.ServiceDescriptors == null)
        throw new ArgumentNullException($"Please register your Services and ViewModels first with the {nameof(RegisterServices)} and {nameof(RegisterViewModels)} methods.");

    if (resetExisiting)
        _services = null;

    if (_services == null)
        _services = ServiceDescriptors.BuildServiceProvider();

    return _services;
}

The BuildServiceProvider method has an additional parameter that allows to reset the existing IServiceProvider. This way, I can keep my existing registrations and just add the new keyed ones dynamically. Please note that you may need to reinitialize your already registered and used ViewModels under certain circumstances after performing the reset.

Accessing a keyed ViewModel

Last but not least, I need to show you how to access a ViewModel by its key. Luckily, this is not that hard:

KeyedViewModel vm4 = IocManager.Current.Services.GetService<IViewModelByKeyResolver<KeyedViewModel>>().GetViewModelByKey("Key4");
KeyedViewModel vm2 = IocManager.Current.Services.GetService<IViewModelByKeyResolver<KeyedViewModel>>().GetViewModelByKey("Key2");

Conclusion

By switching to the CommunityToolkit.MVVM package and utilizing Microsoft’s Extension.DependencyInjection package together with it, my MVVM environment is ready for upcoming challenges like .NET MAUI. I will be able to use it on all .NET platforms and just need to adapt my Xamarin.Forms implementation to others (which I have done already for one of our internal tools at work in WPF). Even keyed ViewModel instance can be used similarly as before, as I showed you in this post.

As always, I hope this post will be helpful for some of you.

Until the next post, happy coding!

Posted by msicc in Dev Stories, Xamarin, 1 comment
Using Microsoft’s Extensions.DependencyInjection package in (Xamarin.Forms) MVVM applications (Part 1) [Updated]

Using Microsoft’s Extensions.DependencyInjection package in (Xamarin.Forms) MVVM applications (Part 1) [Updated]

As some of you might remember, I was always a big fan of the MVVMLight toolkit. As the later one is now deprecated and MAUI around, I took a look at the CommunityToolkit.Mvvm, which is officially the successor to MVVMLight.

As stated in the documentation of the new Toolkit, one could now use the Microsoft’s Extensions.DependencyInjection package for anything related to Inversion of Control (which used to be handled by the SimpleIoc implementation of MVVMLight before). Because this is also the built-in way for .NET 6 and web applications, I decided to adapt it already now for my Xamarin.Forms apps (especially my new one I am currently working on).

[Update] Nuget packages

Please note that while the toolkit’s source is now separated from the Windows CommunityToolkit, the documentation isn’t. This can be confusing (as it was for me). On top of that, there are now two Toolkit MVVM packages:

I thought I got it right when writing this blog post initially. After Brandon Minnick from Microsoft pointed me to the right package, I realized I was not. Up on further research, I found also this discussion in the GitHub repo, stating the one and only will be the CommunityToolkit package. Please use only this one if you are following my tutorials here. I updated all mentions of the Toolkit in this post accordingly.

Default IServiceProvider implementation

The toolkit has a default implementation for the IServiceProvider provided by the Extension.DependencyInjection package. You can read about it here in the documentation and see the source here on GitHub. It focuses heavily on thread safety, its usage is pretty strict, and it does not allow adding ViewModels dynamically. If you do not need stuff like this in your app, you’re probably fine using the Ioc.Default implementation of the toolkit.

Custom IServiceprovider implementation

In TwistReader, the application I am currently working on, I had my requirements easily resolved by the SimpleIoc implementation of the MVVMLight toolkit. With the Extensions.DependencyInjection package, I had to move on with a custom implementation, on which we will have a deeper look in this post. Before you move on reading, make sure you have read the documentation.

IIocManagerBase interface

Of course, I wanted my custom implementation to be reusable. So I extended my existing base interface that my applications need to implement:

public interface IIocManagerBase 
{
    IServiceProvider? Services { get; }
    ServiceCollection? ServiceDescriptors { get; }

    IServiceProvider? BuildServiceProvider(bool resetExisiting = false);

    void Initialize(bool useDefaultNavigationService = true);

    void RegisterServices(bool useDefaultNavigationService);

    void RegisterViewModels();
}

I added the ServiceDescriptors property as well as an IServiceProvider property including a method to (re)build the ServiceProvider if needed. Let’s continue by having a look at the Xamarin.Forms base implementation.

FormsIocManagerBase base class

Building up on the interface before, I created a base implementation for my Xamarin.Forms apps. Let’s go a bit into the details.

In the Initialize method, I am just calling the RegisterServices and the RegisterViewModels methods. One important thing to notice is that I am instantiating the ServiceDescriptors property in the RegisterViewModels method. I also add my default services already to collection there. The RegisterViewModels method remains empty in the base implementation.

public virtual void Initialize(bool useDefaultNavigationService = true)
{
    RegisterServices(useDefaultNavigationService);
    RegisterViewModels();
}

public virtual void RegisterServices(bool useDefaultNavigationService)
{
    this.ServiceDescriptors = new ServiceCollection();

    this.ServiceDescriptors.TryAddSingleton<IDialogService>(DependencyService.Get<IDialogService>());
    this.ServiceDescriptors.TryAddSingleton<IActionSheetService>(new ActionSheetService());

    if (useDefaultNavigationService)
        this.ServiceDescriptors.TryAddSingleton<INavigationService>(new NavigationService());
    else
        System.Diagnostics.Debug.WriteLine("***** DON'T FORGET TO REGISTER YOUR INavigationService INSTANCE(S)!  *****");
}

public virtual void RegisterViewModels()
{
}

Until I switched to CommunityToolkit.Mvvm, this was all I had in there (using SimpleIoc for service registrations). Now that I am using the Extensions.DependencyInjection package, there is some more work to do:

public ServiceCollection? ServiceDescriptors { get; private set; }

private IServiceProvider? _services;

public IServiceProvider? Services => _services ??= BuildServiceProvider();

public IServiceProvider? BuildServiceProvider(bool resetExisiting = false)
{
    if (this.ServiceDescriptors == null)
        throw new ArgumentNullException($"Please register your Services and ViewModels first with the {nameof(RegisterServices)} and {nameof(RegisterViewModels)} methods.");

    if (resetExisiting)
        _services = null;

    if (_services == null)
        _services = ServiceDescriptors.BuildServiceProvider();

    return _services;
}

The code is not that complex, but helps with the IServiceProvider instance handling. The BuildServiceProvider method has a reset flag that allows me to rebuild the provider at runtime. One scenario where we can use this one is for adding ViewModel registrations dynamically during the runtime of our app, but.

IocManager in-app implementation

The next code block shows a typical in-app implementation of my IocManager. You may have noticed I am using the TryAdd{Lifetime} methods already before when adding items to the ServiceCollection. This makes sure that I have always just one registration and does not throw an exception if I try to add it again. If you prefer the exception, just switch to the Add{Lifetime} version.

public class IocManager : FormsIocManagerBase
{
    private static IocManager _instance;

    public static IocManager Current => _instance ??= new IocManager();


    public override void Initialize(bool useDefaultNavigationService = true)
    {
        base.Initialize(useDefaultNavigationService);
    }

    public override void RegisterServices(bool useDefaultNavigationService)
    {
        base.RegisterServices(useDefaultNavigationService);

        this.ServiceDescriptors.TryAddSingleton<ITestService, TestService1>();
        this.ServiceDescriptors.TryAddScoped<ITestService, TestedTestService>();
    }

    public override void RegisterViewModels()
    {
        this.ServiceDescriptors.TryAddSingleton<MainViewModel>();
        this.ServiceDescriptors.TryAddSingleton<SecondaryViewModel>();
    }

    public MainViewModel MainVm => this.Services.GetRequiredService<MainViewModel>();
    public SecondaryViewModel SecondaryVm => this.Services.GetRequiredService<SecondaryViewModel>();
}

For my Xamarin.Forms applications, I always use the IocManager implementation as a singleton. This makes it pretty easy with the different lifetimes on all platforms. As you can see, there is nothing complicated in the registration process, I just add both my services and my ViewModels to the ServiceCollection.

I also have some convenience properties for the most important ViewModels that make Binding easier (as I tend to keep code behind files as clean as possible). If you need a service in another place in your app, and you are not using constructor injection (which gets automatically resolved by the Microsoft.Extensions.DepedencyInjection package), you can get the instance in the same way as I do with the ViewModel instances above.

Conclusion

Creating a custom IServiceProvider implementation is not that hard. The custom implementation allows one to recreate the IServiceProvider (handle with care!) if needed. In the next post, I will show you how to deal with keyed ViewModel instances when using the Extensions.DependencyInjection package.

Have you already used the Microsoft.Extensions.DependencyInjection package with Xamarin.Forms or other platforms (not web)? What are your experiences? If so, leave a comment or chat with me on Twitter!

As always, I hope this post is helpful for some of you.

Until the next post – happy coding, everyone!

Posted by msicc in Dev Stories, Xamarin, 4 comments
Xamarin Forms, the MVVMLight Toolkit and I: Command Chaining

Xamarin Forms, the MVVMLight Toolkit and I: Command Chaining

The problem

Sometimes, we want to invoke a method that is available via code for a control. Due to the abstraction of our MVVM application, the ViewModel has no access to all those methods that are available if we would access the control via code. There are several approaches to solve this problem. In one of my recent projects, I needed to invoke a method of a custom control, which should be routed into the platform renderers I wrote for such a custom control. I remembered that I have indeed read quite a few times about command chaining for such cases and tried to implement it. In the beginning, it may sound weird to do this, but the more often I see this technique, the more I like it.

Simple Demo control

For demo purposes, I created this really simple Xamarin.Forms user control:

<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XfMvvmLight.Controls.CommandChainingDemoControl">
  <ContentView.Content>
      <StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
            <Label x:Name="LabelFilledFromBehind" Margin="12" FontSize="Large" />   
        </StackLayout>
  </ContentView.Content>
</ContentView>

As you can see, there is just a label without text. We will write the necessary code to fill this label with some text just by invoking a method in the code behind. To be able to do so, we need a BindableProperty (once again) to get our foot into the door of the control:

public static BindableProperty DemoCommandProperty = BindableProperty.Create(nameof(DemoCommand), typeof(ICommand), typeof(CommandChainingDemoControl), null, BindingMode.OneWayToSource);

public ICommand DemoCommand
{
    get => (ICommand)GetValue(DemoCommandProperty);
    set => SetValue(DemoCommandProperty, value);
}

The implementation is pretty straightforward. We have done this already during this series, so you should be familiar if you were following. One thing, however, is different. For this BindableProperty, we are using BindingMode.OneWayToSource. By doing so, we are basically making it a read-only property, which sends its changes only down to the ViewModel (the source). If we would not do this, the ViewModel could change the property, which we do not want here.

Now we have the BindableProperty in place, we need to create an instance of the Command that will be sent down to the ViewModel. We are doing this as soon as the control is instantiated in the constructor:

public CommandChainingDemoControl()
      {
          InitializeComponent();

          this.DemoCommand = new Command(() =>
          {
              FillFromBehind();
          });
      }

      private void FillFromBehind()
      {
          this.LabelFilledFromBehind.Text = "Text was empty, but we used command chaining to show this text inside a control.";
      }

That’s all we need to do in the code behind.

ViewModel

For this demo, I created a new page and a corresponding ViewModel in the demo project. Here is the very basic ViewModel code:

using System.Windows.Input;
using GalaSoft.MvvmLight.Command;

namespace XfMvvmLight.ViewModel
{
    public class CommandChainingDemoViewModel : XfNavViewModelBase
    {
        private ICommand _invokeDemoCommand;
        private RelayCommand _demo1Command;

        public CommandChainingDemoViewModel()
        {
        }

        public ICommand InvokeDemoCommand { get => _invokeDemoCommand; set => Set(ref _invokeDemoCommand, value); }

        public RelayCommand Demo1Command => _demo1Command ?? (_demo1Command = new RelayCommand(() =>
        {
            this.InvokeDemoCommand?.Execute(null);
        }));
    }
}

As you can see, the ViewModel includes two Commands. One is the pure ICommand implementation that gets its value from the OneWayToSource-Binding. We are not using MVVMLight’s RelayCommand here to avoid casting between types, which always led to an exception when I tested the implementation first. The second command is bound to a button in the CommandChainingDemoPage and will be the trigger to execute the InvokeDemoCommand.

Final steps

The final steps are just a few simple ones. We need to connect the  InvokeDemoCommand to the user control we created earlier, while we need to bind the Demo1Commandto the corresponding button in the view. This is the page’s code after doing so:

<?xml version="1.0" encoding="utf-8" ?>
<baseCtrl:XfNavContentPage
    xmlns:baseCtrl="clr-namespace:XfMvvmLight.BaseControls" xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:ctrl="clr-namespace:XfMvvmLight.Controls;assembly=XfMvvmLight"
             x:Class="XfMvvmLight.View.CommandChainingDemoPage" RegisteredPageKey="{Binding CommandChainingDemoPageKey, Source=Locator}">
    <ContentPage.BindingContext>
        <Binding Path="CommandChainingDemoVm" Source="{StaticResource Locator}" />
    </ContentPage.BindingContext>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <ctrl:CommandChainingDemoControl Grid.Row="0" DemoCommand="{Binding InvokeDemoCommand, Mode=OneWayToSource}" Margin="12"></ctrl:CommandChainingDemoControl>

        <Button Text="Execute Command Chaining" Command="{Binding Demo1Command}" Margin="12" Grid.Row="1" />

    </Grid>
</baseCtrl:XfNavContentPage>

One thing to point out is that we are also specifying the OneWayToSource binding here once again. It should work with normal binding, but it I recommend to do like I did, which makes the code easier to understand for others (and of course yourself). That’s all – we have now a working command chain that invokes a method inside the user control from our ViewModel.

Conclusion

Command chaining can be a convenient way to invoke actions on controls that are otherwise not possible due to the abstraction of layers in MVVM. Once you got the concept, they are pretty easy to implement. This technique is also usable outside of Xamarin.Forms, so do not hesitate to use it out there. Just remember the needed steps:

  • create a user control (or a derived one if you need to call a method on framework controls)
  • add a BindableProperty/DependecyProperty and set its default binding mode to OneWayToSource
  • instantiate the BindableProperty/DependecyProperty inside the constructor of the user control
  • pass the method call/code into the Action part of the newly created Command  instance
  • create the commands in the ViewModel
  • connect the Commands to your final view implementation

Like I wrote earlier, I came across this (again) when I was writing a custom Xamarin.Forms control with renderers, where I had to invoke methods inside the renderer from my ViewModel. Other techniques that I saw to solve this is using Messengers (be it the one from MVVMLight or the Xamarin.Forms Messenger implementation) or the good old Boolean switch implementation (uses also a BindableProperty/DependecyProperty). I decided to use the command chaining approach as it is pretty elegant in my eyes and not that complicated to implement.

The series’ sample project is updated and available here on Github. Like always, I hope this post is useful for some of you.

Happy coding, everyone!


all articles of this series

title image credit

Posted by msicc in Android, Dev Stories, iOS, UWP, Xamarin, 7 comments
Xamarin Forms, the MVVMLight Toolkit and I: migrating the Forms project and MVVMLight to .NET Standard

Xamarin Forms, the MVVMLight Toolkit and I: migrating the Forms project and MVVMLight to .NET Standard

When I started this series, Xamarin and Xamarin.Forms did not fully support .NET Standard. The sample project for this series has still a portable class library, and as I wanted to blog on another topic, I got reminded that I never updated the project. This post will be all about the change to .NET Standard, focusing on the Xamarin.Forms project as well as the MVVMLight library, which is also available as a .NET Standard version in the meantime.

Step-by-Step

I have done the necessary conversion steps already quite a few times, and to get you through the conversion very quickly, I will show you a set of screenshots. Where applicable, I will provide also some XML snippets that you can copy and paste.

Step 1

step1_unload project

The first step is to unload the project file. To do so, select your Xamarin Forms project in Solution Explorer and right click again on it bring up the context menu. From there, select “Unload Project“.

Step 2

step2_edit_csproj

Next, right-click the selected Xamarin.Forms project again to make the context menu visible once again. From there select “Edit [PROJECTNAME].csproj” to open up the file.

Step 3

step3_replace_all

Now that the file is open, press “CTRL + A”, followed by “DEL”. Seriously, just do it. Once the file is empty, copy these lines and paste them into your now empty file:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>

</Project>

Step 4

step4_open_packages_config

This step is not really necessary, but I want to point one thing out. The old PCL-project type has listed quite a bunch of libraries in the packages.config file. These are all NuGet packages necessary to make the app actually run (they get pulled in during the install of a NuGet package as dependencies). Now that we are converting, we are getting rid of the packages.config file. You can delete right away in the Solution Explorer. We will “install” the needed packages (marked with the arrows) in

Step 5

step5_changed_csproj

For “installing” NuGet packages into the converted project, we are adding a new ItemGroup to the XML-File. The Package System.ValueTuple is referenced – because in this series’ sample project we have some code in there that uses it. The absolute minimum you need to get it running is:

<!--
Template for Nuget Packages:
<ItemGroup>
  <PackageReference Include="" Version="" />
</ItemGroup>
-->

<ItemGroup>
  <!--MvvmLight has changed, so do we!-->
  <PackageReference Include="MvvmLightLibsStd10" Version="5.4.1" />
  <!--needed references-->
  <PackageReference Include="Xamarin.Forms" Version="3.1.0.583944" />
</ItemGroup>

If you have other NuGet packages, just add them into this item group. They will get installed in the correct version if you follow this tutorial until the end.

You might have noticed that the MVVMLight package I am inserting here is not the same as before. This is absolutely true, but for a reason. Laurent Bugnion has published the .NET Standard version quite some time ago. If you want to read his blog post, you can find it here.

The second change I want to outline is DebugTypesettings. These are also not set in that way if you create a new project that is already .NET Standard. In order to enable debugging of your Xamarin.Forms code, however, you absolutely should also add these lines:

<!--these enable debugging in the Forms project-->
<PropertyGroup Condition=" '$(Configuration)'=='Debug' ">
  <DebugType>full</DebugType>
  <DebugSymbols>true</DebugSymbols>
  <GenerateDocumentationFile>false</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)'=='Release' ">
  <DebugType>pdbonly</DebugType>
  <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

These properties provide the debug symbols and the pdb-files a lot of analytic services are dependent on. Simple put, copy and paste them into your project. Hit the “Save”-Button and close the file.

Step 6

step6_delete_obj

Now that we have changed the project file, our already downloaded packages and created dll-files are no longer valid. To make sure we are able to compile, we need to delete the content of the obj folder in the Folder View of the Solution Explorer in Visual Studio.

Step 7

step7_delete_bin

Do the same to the content of the bin folder and switch back to the Solution View by hitting the Folder View-Button again

Step 8

step8_reload_project

Now we are finally able to reload the project as the base conversion is already done.

Step 9

step9_fix errors

Trying to be optimistic and hit the Rebuild button will result in these (and maybe even more) errors. The first one is one that we can solve very fast, while the second one is only the first in a row of errors.

Step 10

step10_delete_properties_folder

To solve the assembly attributes error, just go to the Folder View again and select the Properties folder. Bring up the context menu by right-clicking on it and select the delete option. Confirm the deletion to get rid of the folder. The new project style creates the assembly information based on the project file during build, which is causing the errors.

Step 11

step11_unneeded_reference

Now let’s face the next error. As the MVVMLight .NET Standard version does no longer rely on the CommonServiceLocatorlike before, we are able to remove this reference from our ViewModelLocator.

Step 12

step12_unneeded_instantiation

Of course, we now can also remove the instantiation call for the removed ServiceLocator.

Step 13

step13_replace_servicelocator_calls

In the ViewModel instance reference, replace ServiceLocator.Current with SimpleIoc.Default. Hit the save button again. You might have more errors to fix. Do so, and finally save your ViewModelLocator.

Step 14

step14_rebuild_succeeded

After all the work is done, we are now able to compile the .NET Standard version of our Xamarin.Forms project. If you fixed all of your errors in the step before, you should achieve a similar result like me and get a success message.

Final steps

Now that the Xamarin.Forms project is built, you might want to try to build all other projects in the solution as well.  The changed structure of the Xamarin.Forms project will have an impact also on the platform projects, that’s why I absolutely recommend deleting the contents of bin and object folders there, too. This will solve you a lot of work to get things to compile again.

As always, I hope this post is helpful for some of you. If you have any questions, feel free to ping me on my social accounts or write a comment below. The next post in this series will involve again more code, so stay tuned –  it will be out soon.

Please find the updated sample project here on GitHub.

Happy coding, everyone!

title image credit

Posted by msicc in Dev Stories, Xamarin, 1 comment