MVVM – Formal Pattern to Manage State

mvvmreduxstatewpf

I have started learning about Redux and React in the web-world, and the more I learn about it the more I'm realizing how painful state management is in the desktop-world with WPF's MVVM-style architecture (using Caliburn specifically to bind Views to ViewModels).

Redux has a few simple principles that dictate how state should be managed, making UI updates, event handling and state changes much more predictable. The principles are:

  • A single source of truth (all mutable state is stored in a single shared object).
  • State is read-only. It cannot be modified by components through-out the code, which is typically what happens in WPF.
  • State can only be modified by pure functions.

WPF's MVVM architecture allows you to build interactive views very quickly, but debugging issues when various viewmodels and events all change state is a nightmare. For example: an event fired that changed a view and tried to set a default tab, but the data has not finished loading asynchronously from a web service so the tab doesn't exist (yet) so nothing happens

I've spent hours drawing diagrams to try and understand complex interactions between inter-related viewModels components that update one-another.

I understand that Redux aims to solve some of this state unpredictability. Is there something similar, or an architectural pattern that would fit nicely with WPF to help manage state better? I'm not sure how well the Redux principles would work in .NET as I have not tried them yet. Perhaps somebody has some experience that can give some advice?

Best Answer

I think I know what you mean. Basically you solve the problem by adding either a 'controller' or a 'master' viewmodel (excuse psudocode)

ie

public class MasterVM
{
    public ChildVM View1 {get;set;}
    public ChildVM View2 {get;set;}

    private Data data;
    public MasterVM()
    {
        View1.OnEvent += updateData;
    }

    private Action<int> updateData(int value)
    {
         View2.Value = value;
    }
}

when you do this with the Mediator Pattern, I think of the class as a Controller. ie.

public class Controller
{
    public Controller(MediatorService m)
    {
        m.Subscribe("valueupdated", updateData);
    }

    private Action<int> updateData(int value)
    {
         m.Publish("showvalue", value);
    }
}

public class View2
{
    public View2(MediatorService m)
    {
        m.Subscribe("showvalue", (int v)=> {Value = v;});
    }
}

This kind of thing allows you to put your 'flow logic' or Event Orchestration in these high level persisted classes and keep the VMs code light. If you want to change 'when user clicks BUY, the order is processed' kind of things you know to look in the 'OrderFlowController' or 'OrderProcessVM' or however you want to name them. Rather than a combination of the BasketVM, PaymentVM, 3dSecureVM etc etc

So in your specific example of the 'tab not ready yet' you might have

public class Controller
{
    bool dataLoadCompleted;
    public Controller(MediatorService m)
    {
        m.Subscribe("setTabRequest", setTab); //message from view model with set tab button
        m.Subscribe("dataLoadComplete", dataLoadComplete); //message from data loading view model or some other controller?
    }

    private Action<int> setTab(int value)
    {
         if(!dataLoadCompleted)
         {
             m.Publish("error", "Please wait for data to load"); //message for error alert view model
         }
         else
         {
             m.Publish("setDefaultTab", value); //message for tab viewmodel
         }
    }

    private Action dataLoadComplete()
    {
         //persist state;
         dataLoadCompleted = true;
    }
}