Converting My Contacts: Step 1. SetttingsPage, Middleware
June 09, 2020
I’m writing a series of posts on converting My Contacts app using Laconic.
Laconic can be used to write entire apps, but it was designed to fit into existing Xamarin.Forms code. You can start small: replace a view, replace a page. To demonstrate how it can be done I’ve decided to take a full-featured sample app from Xamarin, and convert it to using Laconic, step by step.
By the series finale, we’re going to have all XAML removed, all of the MVVM-mandated ceremony eliminated. The result is going to be a cleaner architecture and cleaner code while keeping the functionality the same.
Step 1: SettingsPage
We are going to start with the Settings page:

Converting the UI code is pretty straightforward. A big part of the job can be done with a bunch of search/replace. This:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Margin="10"
Style="{DynamicResource LargeLabelStyle}"
Text="Settings"
VerticalOptions="Center"
HorizontalOptions="{OnPlatform iOS=Center, Default=Start}"
Grid.ColumnSpan="2"/>
<Button
.../>
becomes this:
new Grid {
RowDefinitions = "Auto, *",
ColumnDefinitions = "*, Auto",
["title", columnSpan: 2] = new LargeLabel(state)
{
Text = "Settings",
Margin = 10,
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.Center,
},
["closeButton", column: 1] = new Button
{...
}
One small observation: in the original SettingsPage.xaml
there’s quite a bit of copy/pasted XAML
with only the value of the BackgroundColor
attribute changing:
<BoxView
BackgroundColor="{DynamicResource SystemBlue}"
HeightRequest="20"
HorizontalOptions="FillAndExpand" />
<BoxView
BackgroundColor="{DynamicResource SystemGreen}"
HeightRequest="20"
HorizontalOptions="FillAndExpand" />
... Seven more BoxView elements
We feel your pain, James.
Encapsulating bits with XAML is quite a chore. For a reusable view you have to have a separate .xaml
file with the accompanying .xaml.cs
, and write plenty of boilerplate code for bindable properties.
While with Laconic, since it’s plain C#, the task is trivial:
static BoxView ColorRow(Color color) => new BoxView
{
BackgroundColor = color, HeightRequest = 20, HorizontalOptions = LayoutOptions.FillAndExpand
};
There are two new files:
State.cs
contains our new application state classes, and a signal class used to notify that the user changed
the app theme. Our state is immutable, and the main reducer is a pure function.
Components.cs
contains blueprint classes which are reused throughout the UI. They replace a few styles in App.xaml
.
These classes are purish, they don’t operate on anything but the values passed to their constructors.
Remember the diagram from Introduction to Laconic? Laconic gently pushes us towards using pure functions. The benefits are numerous: the flow is straightforward; composing a UI from smaller bits is easier, since there are no side effects; unit testing is super simple.
And here we’re facing the first challenge: No app code lives in a pure blue bubble.
An app needs to make network requests, read files, handle device notifications. Activities like that are inherently
impure. The app we’re converting uses DynamicResource
bindings throughout, and those resource values
are changed from the Settings page.
What are we going to do here? The answer is Laconic’s middleware.
Middleware
Middleware support in Laconic is a way for an app developer to plug into the Signal -> Reducer -> Blueprint Maker cycle. It’s modeled after ASP.NET Core middleware, and should be instantly familiar to most Xamarin.Forms developers.
A middleware is a function that takes context and a reference to the next middleware in the pipeline and returns context. The more complete picture of the execution flow with Laconic looks like this:
Much like ASP.NET Core middleware, Laconic’s middleware can run code before and/or after the next middleware in the pipeline, or it can short-circuit the evaluation of other steps. The middleware can inspect the state and can modify it.
The main reducer is at the end of the middleware pipeline. Think of it as the code inside of ASP.NET Core Controller’s Action.
While reducers and blueprint makers should be pure, it’s OK for middleware to be not. Actually, this is the intended use. Use it for logging, for receiving updates from the device timer, etc.
Luckily enough, in the original My Contacts app, all the functionality we need in our new Settings page is encapsulated in a couple of static methods. We just need to wrap it in our middleware:
_binder.UseMiddleware((context, next) =>
{
context = next(context);
if (context.Signal is SetThemeSignal t)
{
Settings.ThemeOption = context.State.Themes.Length == 3 ? t.Payload : t.Payload + 1;
ThemeHelper.ChangeTheme(t.Payload);
return context.WithState( new State (
new Colors(Application.Current.Resources),
(
Device.GetNamedSize(Xamarin.Forms.NamedSize.Large, typeof(Label)),
Device.GetNamedSize(Xamarin.Forms.NamedSize.Large, typeof(Label))
),
context.State.Themes,
context.State.SelectedTheme)
);
}
return context;
});
And dismissing modal SettingsPage
is a s simple as:
_binder.UseMiddleware((context, next) =>
{
if (context.Signal.Payload = "closeSettings")
Navigation.PopModalAsync();
return next(context);
});
Summary
Let’s calculate some stats.
Before:
------------------------------------------------------------------
Language files blank comment code
------------------------------------------------------------------
C# 51 435 250 1952
XAML 9 103 19 922
MSBuild script 6 13 7 597
XML 12 20 32 476
JSON 4 0 0 157
Bourne Shell 3 6 2 17
------------------------------------------------------------------
SUM: 85 577 310 4121
------------------------------------------------------------------
Character count, XAML and C# files: 130,047
After:
------------------------------------------------------------------
Language files blank comment code
------------------------------------------------------------------
C# 52 450 252 2120
XAML 8 96 19 813
MSBuild script 6 11 7 578
XML 12 20 32 476
JSON 4 0 0 157
Bourne Shell 3 6 2 17
------------------------------------------------------------------
SUM: 85 583 312 4161
------------------------------------------------------------------
Character count, XAML and C# files: 132,307
Well, we have an increase in numbers… Laconic isn’t being laconic, does it? Don’t worry. We had to lay down the initial infrastructure and we’re going to see a dramatic decrease in the amount of code with later stages of the conversion. Look at the bright side: one XAML file and one view model were eliminated, yay!
I would argue that the new code is easier to understand and to maintain. We now have an immutable state,
we have pure functions for modifying it.
The functions for creating the UI are pure too. There’s no data binding, no indirection.
Since on every change of the state the code creates an entire blueprint (virtual view) tree
everything can be read top-down, starting with MainContent
function.
Check out the the code for Step 1 of the conversion: https://github.com/shirshov/app-contacts/tree/step1-settings-page
Don’t forget to do git submodule init && git submodule update