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.