Apprentice Dev Blog: Weeks 7 and 8 - .NET Maui and MVVMS

  • Monday, Jul 11, 2022
A person stands, looking up into the night sky. They are quite far from the camera and shown only in silhouette against a multi coloured starfield. The sky is a yellow hue, going through purple into green before becoming black.

The cover image for this post is by Domenico Loia

This blog post was written by Alex. Alex is our apprentice for 2022, and this will be their final dev blog entry with us.


Jamie here - to jump directly to Alex’s log for this week, click here

I wanted to hold off publishing the penultimate dev blog by Alex

remember that Alex is NOT their real name

as the final few weeks of their project have been largely clearing things up and looking at design patterns. But this week marks the final one that we’ll be spending with Alex, and I wanted to let them talk about how they’ve been wrapping things up.


Alex’s Devlog

Hi, I’m Alex and am currently studying a Digital T-Level course, am taking my industry experience with RJJ Software.

Part of these final few weeks has been spent cleaning up the code base (removing unused NuGet packages, optimising some code, etc.), and getting the final few parts of functionality in place.

When I started this project Jamie had told me not to expect to complete it. He’d said that this wasn’t meant to be a mark against me or my abilities, but more because he wanted to cover a lot of things that they didn’t have much time to cover on the T-Level course, and that we wouldn’t have the time to do that and complete the project. Some of these things where:

  • SOLID
  • git and source control
  • Unit testing
  • .NET Maui

This last item was really quite a large ask, because .NET Maui was released two weeks into my time at RJJ, which meant that it was so new that the documentation hadn’t caught up yet. Thankfully, Jamie was able to share two video series with me which helped make things make sense:

These are indispensible resources to anyone who is new to .NET Maui, and my project was built primarily from these two sources. Whenever Jamie wasn’t available to help

Jamie here: this wasn’t that often, but I sometimes had other responsibilities to take care of

I would consult my notes from these two resources.

That being said, there were a few times that boh Jamie and I were caught out, specifically around events and two-way binding and commands in .NET Maui.

Binding Commands

As an example, all buttons have a Command property and these need to be mapped to a method which is decorated with the [CommandRelay] attribute. But something that we forgot a few times is that, regardless of you call the method in the code-behind, you have to refer to is with the Command suffix in the XAML Command property. Here’s some code illustrating it:

<VerticalStackLayout Spacing="5">
    <StackLayout Grid.Row="1" Grid.Column="1">
        <Button
            Text="View"
            TextColor="White"
            FontAttributes="Bold"
            HorizontalOptions="Center"
            VerticalOptions="Center"
            HeightRequest="60"
            WidthRequest="150"
            BackgroundColor="MediumSlateBlue"
            Command="{Binding GetClientsCommand}"
            IsEnabled="{Binding IsNotBusy}"
            x:Name="btn_View"/>
    </StackLayout>
</VerticalStackLayout>

The important bit in the above code is Command="{Binding GetClientsCommand}". This tells .NET Maui to call the GetClients command on the relevant model, which in our case looks like this:

// Note the RelayCommand attribute
[RelayCommand]
// Also note the lack of "Command" in the name
public void GetClients()
{
    if (IsBusy)
    {
        return;
    }
    IsBusy = true;

    // call the relevant service and set up the clients list on the ViewModel
    
    IsRefreshing = false;
    IsBusy = false;
}

Yes the above code is synchronous, but it’s serving a point rather than being an example of a best practice. The things that Jamie and I slipped up with here were:

  • Not adding the [RelayCommand] attribute to the method
  • Forgetting that the convention in the binding was to add the Command suffix

Two Way Binding

Having missed out on XAML when it was first introduced in WPF and WinUI, Jamie came to XAML with an HTML mindset.

Jamie here: it’s true. It also had it stuck on my head that XAML would do two-way binding automatically for us. Which I was wrong about

Having worked with things like React and Knockout (which take care of two-way binding), he had become convinced that two-way mapping was handled automatically. But that wasn’t the truth, as it works a LITTLE like WinForms - in that you need to tell .NET Maui/XAML that you want to call back when a value has changed.

Let’s say that you have a text box that you want to map to a property on your ViewModel:

<StackLayout Grid.Row="5" Grid.Column="1">
    <Entry
        Placeholder="Enter here..."
        PlaceholderColor="White"
        TextColor="White"
        FontSize="16"
        Keyboard="Numeric"
        TextChanged="txt_lineItemCost_TextChanged"
        HorizontalOptions="Center"
        VerticalOptions="Center"
        x:Name="txt_lineItemCost">
    </Entry>
</StackLayout>

In the XAML above, we have a text input box called txt_lineItemCost which isn’t bound to a value, but we have set up a binding to a method called txt_lineItemCost_TextChanged when the value changes (i.e. when the user types into the text box).

This method looks a little like the following:

private void txt_lineItemCost_TextChanged(object sender, TextChangedEventArgs e)
{
    var newValue = txt_lineItemCost.Text;
    if (CheckEmpty(newValue) && TryParseToDouble(newValue))
    {
        ((InvoiceGeneratorViewModel)this.BindingContext)._lineItemVm.Cost = Convert.ToDouble(newValue);
    }
}

Where CheckEmpty is a method which wraps around string.IsNullOrWhiteSpace for readability and DRY reasons.


Jamie here.

One thing to note is the use of the Keyboard="Numeric" in the XAML. This ensures that the keyboard shown to the user in a context where they will be served a software keyboard (i.e. a mobile phone, a tablet, or in an accessibility situation) that they will be given a numeric keyboard. This will stop the user from entering non-numerical values in those contexts.

However, it will not stop the user outside of that context from entering arbitrary text (i.e. in a desktop application where a hardware keyboard is used). Just something to be aware of.


MVVMS

This is a wonderful design pattern that James Montemagno introduced in the following video:

He admits that it’s not an “official” design pattern, and that it’s “just” MVVM with a service added in." I’ve been using this pattern throughout the development of this app without really knowing it. MVVMS stands for:

  • Model
  • View
  • View-Model
  • Service

It’s a relatively simple pattern to follow in that you continue to use the MVVM pattern, but inject one of more services to use. James says that he prefers to inject concrete implementations of services (i.e. classes) rather than interfaces, but concedes that it’s also possible (and might be preferable, depending on your use-case) to use interfaces instead.

The example that James gives is if you have a Model which uses an instance of the HttpClient. In that instance, it could be cleaner to inject a class which wraps around the HttpClient. That way, the Model doesn’t need to be concerned with the details of how the HttpClient class is set up

Jamie here: something something Single-responsibility Principle

And it becomes almost trivial to change in the future.

Suppose you decide that you want to move away from the HttpClient class because you’re no longer talking to a WebApi using Http and that you decide to move over to gRPC or some other technology. You only need to change the implementation of your service, rather than having to change all of your Models. The Model only needs to know about the method that it’s calling, the arguments, and it’s return type.

Let’s see that in code:

public class SuperAmazingModel
{

    private readonly IClientService _clientService;
    
    public SuperAmazingModel (IClientService clientService)
    {
        _clientService = clientService;
    }
    
    // Note the RelayCommand attribute
    [RelayCommand]
    // Also note the lack of "Command" in the name
    public void GetClients()
    {
        if (IsBusy)
        {
            return;
        }
        IsBusy = true;
    
        var clients = _clientService.GetPagedClients();
        
        IsRefreshing = false;
        IsBusy = false;
    }
}

public interface IClientService
{
    Paged<ClientViewModel> GetPagedClients(int pageNumber, int perPage);
}

public class ClientService : IClientService
{
    private readonly HttpClient _httpClient;

    public ClientService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public Paged<ClientViewModel> GetPagedClients(int pageNumber, int perPage)
    {
        // Do something with _httpClient_
    }
}

And when we migrate away from HttpClient, we only need to change the ClientService because SuperAmazingModel doesn’t need to know about how classes which inherit from IClientService work, just that they return a Paged<ClientViewModel> when GetPagedClients is called with two ints.

In Closing

I’ve really enjoyed my time with RJJ and have had experience playing with some bleeding edge technologies whilst working alongside Jamie. Working with him on building something with .NET Maui has given me a legitimate chance to play around with some technology that I don’t think I’d have had the chance to otherwise.

I’ve also picked up a lot of information and skills along the way which should help me in my career. From SOLID to DRY vs WET (“write everything twice”, the opposite of DRY), and from git and gitflow to a little on Clean Architecture and Domain-Driven design. Some of these points have been shared during pair programming sessions, and some where shared during downtime between sessions - whilst taking a break or getting lunch, for instance.

There’s a lot to learn with .NET Maui, and a few things which caused both Jamie and me to “fall into the pit of failure”, but that was mostly based on our misunderstanding. Most of it is actually designed to let you “fall into the pit of success,” which has been refreshing. In some ways .NET Maui is completely different to WinForms, yet very similar in others.


Jamie again.

I’m incredibly please with Alex’s progress throughout their time with us. I knew going into the project that we wouldn’t leave it with a full functional app. But what we do have is a cross-platform application which scales from the desktop down to a mobile phone - it will scale down to a smart watch too, but we didn’t have a chance to try that out. The code is hosted in GitHub, it has a CI/CD build pipeline, which produces:

  • A Windows binary
  • An iOS app
  • An Android App
  • A MacOS desktop app

Each of which can be downloaded and installed onto a device of your choice.

The application was built using gitflow and PRs, meaning that we could both review each others work as the features for the application was built up. This gave both Alex and myself a chance to learn from each other.

it’s also worth noting that Alex taught me a few things about .NET Maui thorugh lunch-and-learn sessions.

The application communicates with a series of WebAPIs using the HttpClient class, and uses dependency injection to inject a service into each relevant Model - meaning that we only have one place where the HttpClient will need to be updated, should it ever need to be updated.

The WebApis communicate with several SQL databases and use Entity Framework Core to do that communication. We haven’t had much of a chance to talk about this part of the application, but I’m planning a companion piece which talks about the full stack.

I’m incredibly excited to see where Alex’s career takes them, and I’m hopeful that their time with us has been productive, educating, and exciting.