One of the problems we currently have is caused by lack of updates - our application model can change without our view-models being notified that the change has happened, leaving our user interface showing stale information. To solve this, we’ll extend our Redux store with subscriptions, to allow each view-models to be proactively notified when things change.

There are a multitude of different ways this could be done.

For example, we could simply publish an event on the redux store, allowing anyone to be notified when the state of the store changes.

// Declaration on ReduxStore<T>
public event EventHandler<StoreChangedEventArgs> StoreChanged { add; remove; }

// Consumption within a ViewModel
_store.StoreChanged += WhenStoreChanged;

private void WhenStoreChanged(object sender, StoreChangedEventArgs args) { ... }

This might be a straightforward and simple, but it would also result in a lot of repetitive boilerplate as every ViewModel repeats the same change detection logic. We can do better.

Let us create a dedicated subscription object, one that ensures we only get notifications when things have changed. To our interface, IReduxStore<T>, add the following declaration:

IDisposable Subscribe<V>(
    Func<T, V> reader, 
    Action<V> whenChanged) 
    where V : System.IEquatable<V>;

This method creates a subscription to the content of the store. The reader function reads a value from the store that can be tested for equality, and the whenChanged action will be called when the value is different. This follows our established pattern of short circuiting, ensuring we only send notifications when required. By returning IDisposable, we’re giving our consumers a convenient (and idiomatic) way to release the subscription when they’re done.

To encapsulate the subscription, we’ll create a new class: ReduxSubscription<T, V>. An initial design for the class might look something like this:

public class ReduxSubscription<T,V>: IDisposable
{
    public ReduxSubscription(
        ReduxStore<T> store,
        Func<T,V> reader,
        Action<V> whenChanged
    )
    { }
    public void Release() { }
}
  • store is a reference to our owning Redux Store;
  • reader is a function to read the value of interest from the store state; and
  • whenChanged is an action to trigger when the read value changes.

You can imagine how the subscription would use store to register itself inside the constructor, and would also deregister itself when the subscription was released.

While being a very object oriented design, this approach introduces strong coupling between ReduxStore<T> and ReduxSubscription<T,V>. Such coupling makes it harder to unit test ReduxSubscription in isolation. It also complicates the constructor a little, requiring registration with the store.

Can we lower the coupling between these classes and simplify the constructor at the same time?

Yes, by replacing the store parameter with an action to call when the subscription is released. We’ll call that whenReleased.

Our next issue is a common one when working with Generics. The store itself has only one type parameter, one axis of variation. Our subscription class has two type parameters, two axes of variation. For good reasons, we can’t simply declare a List<ReduxSubscription<T,*>> to wrap them all up in a single list. We need to introduce a common type, one that is only variant on T.

To this end, I’ve broken the class into two pieces, each with a single responsibility. A parent class, ReduxSubscription<T> that handles releasing the subscription; and a child class, ReduxSubscription<T,V> that handles our notifications.

public abstract class ReduxSubscription<T> : IDisposable
{

This only has one type parameter, so we will be able to keep them all in a single container within our store.

    // A reference to the action we'll trigger when the subscription is released.
    private readonly Action<ReduxSubscription<T>> _whenReleased;

    protected ReduxSubscription(Action<ReduxSubscription<T>> whenReleased)
    {
        _whenReleased = whenReleased 
            ?? throw new ArgumentNullException(nameof(whenReleased));
    }

Our base class has a single responsibility; to handle the lifetime of the subscription. The constructor accepts an action, which we’ll call when the subscription is released.

    public bool Released { get; private set; }

    public abstract void Publish(T state);

The Publish() method will be overridden by our subclass to actually share information.

When we’re finished with the subscription, a call to Release() will deactivate the subscription so that we’re no longer receiving notifications. We implement Dispose() as a direct call to this.

    private void Release()
    {
        if (!Released)
        {
            Released = true;
            _whenReleased(this);
        }
    }

    public void Dispose() => Release();
}

We use the Released property to ensure that we only invoke our whenReleased action once. For now, we’re using an implementation without locking - if/when we start running multiple threads, we’ll need to add some protection against concurrency errors.

Our subclass is also relatively simple, extending the base class to add smart notifications.

public sealed class ReduxSubscription<T, V> 
    : ReduxSubscription<T>
    where V : IEquatable<V>
{

The constraint is to ensure that we can do effective change notifications by only notifying subscribers when the value has actually changed.

Our private members are largely predictable; members used to capture the delegates we’re passed, and the last value we’ve seen.

    private readonly Func<T, V> _reader;
    private readonly Action<V> _whenChanged;

    private readonly EqualityComparer<V> _comparer = EqualityComparer<V>.Default;

    private V _lastValue;

As usual, our constructor just captures parameter values, though we do have a couple of guard clauses to ensure we don’t have any errant null values floating around.

    public ReduxSubscription(
        Func<T, V> reader,
        Action<V> whenChanged,
        Action<ReduxSubscription<T>> whenReleased)
        : base(whenReleased)
    {
        _reader = reader 
            ?? throw new ArgumentNullException(nameof(reader));
        _whenChanged = whenChanged 
            ?? throw new ArgumentNullException(nameof(whenChanged));
    }

Once we’ve got everything set up, actual publication is pretty straightforward.

   public override void Publish(T state)
    {
        var value = _reader(state);
        if (!Released && !_comparer.Equals(value, _lastValue))
        {
            _lastValue = value;
            _whenChanged(value);
        }
    }
}

A few highlights worth noting about this method.

  • First time the subscription is published, lastValue will be null, and we’ll sent a potentially redundant notification. We could avoid this by invoking reader during construction, but that would also require us to pass in the current state from our redux store.

  • Updates to _lastValue happen before we send the notification to short circuit any potential infinite notification loops. If we updated _lastValue after calling _whenChanged, we’d be vulnerable if the code we called in turn triggered another call to Publish().

  • We need to check the Released property to ensure we don’t send a notification from a released subscription. It’s possible (even likely) that subscriptions will be released by consumers in response to messages received; we’d prefer our consumers to not have to worry about extra notifications coming in after Release().

Now we extend our store to allow subscriptions. We begin by providing a way for consumers to create subscriptions:

// Set of all our current subscriptions
private HashSet<ReduxSubscription<T>> _subscriptions 
    = new HashSet<ReduxSubscription<T>>();

public IDisposable Subscribe<V>(
    Func<T, V> reader,
    Action<V> whenChanged)
    where V: IEquatable<V>
{
    var subscription = new ReduxSubscription<T, V>(
        reader 
            ?? throw new ArgumentNullException(nameof(reader)),
        whenChanged 
            ?? throw new ArgumentNullException(nameof(whenChanged)),
        ReleaseSubscription);

    _subscriptions.Add(subscription);

    return subscription;
}

private void ReleaseSubscription(ReduxSubscription<T> subscription)
{
    _subscriptions.Remove(subscription);
}

Next, we add to our Dispatch() method, so that all the subscriptions are given a chance to update when our state might be updated.

public void Dispatch(IReduxMessage message)
{
    // existing code elided ...

    foreach (var subscription in _subscriptions.ToList())
    {
        subscription.Publish(State);
    }
}

Taking a copy of the set of subscriptions is necessary to avoid getting an exception if any of our subscriptions get removed during publication.

Lastly, we publish the count of our current subscriptions, largely to make it easier to write some unit tests.

public int SubscriptionCount => _subscriptions.Count;

Next time, we’ll modify our ViewModels to stay up to date by using subscriptions.

Prior post in this series:
Dependency Injection: Views
Next post in this series:
ViewModel Subscriptions

Comments

blog comments powered by Disqus