With subscriptions wired up to keep our view models updated, we can run our application and start clicking around. When we select a word, we can trace through the flow of messages to see how everything updates. But, it’s easy to crash. Let’s debug that crash and work out how to make the application more robust.

If you select a word in the list, and then control-click on that word a second time, the application will crash. Let’s work through fixing up this defect.

We’re getting an ArgumentNullException, with the message “Message=Value cannot be null. (Parameter ‘word’)” in the preamble for this method:

public VocabularyBrowserScreen WithSelection(
    VocabularyWord word)
{
    if (Equals(Selection, word 
        ?? throw new ArgumentNullException(nameof(word))))
    {
        return this;
    }

While a good example of fail fast, this exception is still too late to tell us exactly where the problem came from.

Looking at the stack trace, WithSelection is called from the Reduce() method of VocabularyBrowserScreenReducer, and the parameter value originates from an instance of SelectWordMessage.

case SelectWordMessage m:
    return currentState.UpdateScreen(
        (VocabularyBrowserScreen s) => s.WithSelection(m.Word));

Looking at SelectWordMessage, we see that there’s nothing to stop its Word property from being null. We could decide that this is okay, but there is history showing that null values cause bugs. So let’s take a different approach.

Modify the constructor for SelectWordMessage by adding a guard clause so that it doesn’t accept a null selection:

public SelectWordMessage(VocabularyWord word)
{
    Word = word ?? throw new ArgumentNullException(nameof(word));
}

Now, when we reproduce the bug, we get an earlier exception.

In VocabularyBrowserViewModel, our Selection property is being set to null via DataBinding with the ListBox on screen.

What’s going on?

Ahh - it is possible for a WPF ListBox control to have no selection - and we don’t currently handle that case. We’re blindly creating a SelectWordMessage instance, even when we have no selection at all:

public VocabularyWord Selection
{
    get => _selection;
    set => UpdateProperty(
        ref _selection,
        value,
        sel => _store.Dispatch(new SelectWordMessage(sel)));
}

Now our required fix becomes clear - we need to send a different kind of message when the selection has been removed. The message definition is trivial - most of the value comes from it being a separate type.

public class ClearSelectedWordMessage : IReduxMessage
{
    public override string ToString() => "Clear selection";
}

When the Selection property is changed, we need to ensure we send the right kind of message:

public VocabularyWord Selection
{
    get => _selection;
    set => UpdateProperty(
        ref _selection,
        value,
        sel => _store.Dispatch(CreateSelectionMessage(sel)));
}

private static IReduxMessage CreateSelectionMessage(VocabularyWord selection)
{
    if (selection is null)
    {
        return new ClearSelectedWordMessage();
    }

    return new SelectWordMessage(selection);
}

After the message is sent, our VocabularyBrowserScreenReducer needs to handle it by removing the selection from our application state:

case ClearSelectedWordMessage _:
    return currentState.UpdateScreen(
        (VocabularyBrowserScreen s) => s.WithNoSelection());

This in turn, requires modifying VocabularyBrowserScreen by adding the new method:

public VocabularyBrowserScreen WithNoSelection()
{
    if (Selection is null)
    {
        return this;
    }

    return new VocabularyBrowserScreen(this, selection: null);
}

While this approach takes more code, it’s superior because it makes support for “no selection” explicit in the object model; it doesn’t happen “by accident”, as a side effect of tolerating null values. Being explicit in this way is valuable as our application evolves over time because future maintainers don’t have to guess (perhaps wrongly) our intentions.

Prior post in this series:
ViewModel Subscriptions
Next post in this series:
Commands and CommandBinding

Comments

blog comments powered by Disqus