Omnitalented

Converting My Contacts: Step 3a. Restoring input validation

September 02, 2020

This article is a part of a series on converting My Contacts app using Laconic. The start of the series is here.

Step 3a: Restoring Input Validation

When talking about the Step 3 of the conversion I made a promise to restore input validation.

It’s rather simple, really. As always, when leaving the pure world of Laconic we use a bit of middleware in our App.xaml.cs:

_binder.UseMiddleware((context, next) =>
{
    if (context.Signal is DisplayAlert a)
    {
        xf.Device.BeginInvokeOnMainThread(() => { 
            navPage.DisplayAlert(a.Title, a.Message, "OK"); 
        });
    }
    return next(context);
});

And our Save button calls a validation method instead of sending a signal immediately:

...
new ToolbarItem {
    Clicked = () => Proceed(contact)
}

...

static Signal Proceed(Contact contact)
{
    if (new [] {contact.LastName, contact.FirstName}.Any(x => string.IsNullOrEmpty(x.Trim())))
        return new DisplayAlert("Invalid name!", "A Contact must have both a first and last name.");

    var validAddress = new[] {contact.Street, contact.City, contact.State}.All(x => !string.IsNullOrEmpty(x.Trim()))
        || !string.IsNullOrEmpty(contact.PostalCode);

    if (!validAddress)
        return new DisplayAlert(
            "Invalid address!",
            "You must enter either a street, city, and state combination, or a postal code.");

    return new SaveContact(contact);
}

You might argue that ContactEditor class breaks SRP: it contains both code for constructing the UI and code for validating user input. Well, I don’t see it this way. Despite of having class keyword in its declaration ContactEditor doesn’t do any of the OOP stuff. There’s no encapsulated data, and obviously no instance is ever created (the class is static). It’s a collection of pure functions. Think of it as a module. This module takes a contact to be modified, and returns a value that can be either “save this contact to permanent storage” or “more input from user required” in the form of a signal. The caller then decides what to do with the returned value.

Here is the commit for this step.