Settings in a New Light - Shiny Style

Published on Friday, May 3, 2019

I know - Preferences & Settings are a dime a dozen these days, but I'm such a big advocated of decoupled software that I had to do things differently. With Shiny, I had a chance to move one of the features that I loved from my ACR Settings plugin over and give it a good home in a DI universe of awesome!

You've probably seen code similar to this before:


public static class AppSettings
{
    public static string ApiBaseUri
    {
        get => Settings.Get("ApiBaseUri");
        set => Settings.Set("ApiBaseUri", value);
    }
}

Why is this not ideal?

Several reasons

  • It is not a testable piece of code - not that settings usually have to be testable, but they are often used in tests
  • It creates some coupling (yes we can get around this though)
  • It uses some boilerplate code
  • It isn't necessarily fun to wire through your app
  • You can't react to changes in your settings

Settings in a different light

Often people associate the INotifyPropertyChanged interface the ViewModel interface. It is, but it can be used for many other cool things. Let's look at this in a different way:

public class AppSettings : Shiny.NotifyPropertyChanged
{
    public AppSettings()
    {
        // values set here are like defaults and not pushed settings 
        // they are overwritten by the settings binding if they were set
        this.RememberText = "Hello"; 
    }


    string rememberText;
    public string RememberText 
    { 
        get => this.rememberText
        set => this.Set(ref this.rememberText, value);
    }


    bool isEnabled;
    public bool IsEnabled
    { 
        get => this.isEnabled;
        set => this.Set(ref this.isEnabled, value);
    }


    DateTime? lastUpdated;
    public DateTime? LastUpdated 
    { 
        get => this.lastUpdated;
        private set => this.Set(ref this.lastUpdated, value);
    }
}

Now, you can definitely argue that viewmodels contain their own set of boilerplate, but we've got so many tools to take care of things like this now a days such as ReactiveUI.Fody & PropertyChanged.Fody courtesy of Simon Cropp!

Messaging in a new REACTIVE light:

Using the AppSettings class above, let's set that LastUpdated according to changes in the Remember Me


// adding to the constructor above
public AppSettings()
{
    // whenever a property changes, update the date (and remember it to)
    this.WhenAnyProperty(x => x.IsEnabled).Subscribe(_ => this.LastUpdated = DateTime.Now);
    this.WhenAnyProperty(x => x.RememberText).Subscribe(_ => this.LastUpdated = DateTime.Now);
}


// to register this as a service with Shiny - add the following to your Startup

public class MyStartup : Shiny.Startup
{
    public override void ConfigureServices(IServiceCollection services)
    {
        services.RegisterSettings<AppSettings>();
    }
}

That magical little method WhenAnyProperty is from a bunch of RX addons with Shiny.Core.

What about doing this on my ViewModel?

Sure, just make sure to unbind the ViewModel from settings when it is going away - Below is an example using Prism:

public class MyViewModel : Prism.Mvvm.BindableBase, Prism.AppModel.IPageLifecycleAware
{
    readonly ISettings settings;


    public MyViewModel(ISettings settings)
    {
        this.settings = settings;
    }


    public void OnAppearing()
    {
        settings.Bind(this);
    }


    public void OnDisappearing()
    {
        settings.UnBind(this);
    }



    string rememberText;
    public string RememberText 
    { 
        get => this.rememberText
        set => this.SetProperty(ref this.rememberText, value);
    }


    bool isEnabled;
    public bool IsEnabled
    { 
        get => this.isEnabled;
        set => this.SetProperty(ref this.isEnabled, value);
    }
}

Closing Thoughts

Pretty epic right!? This can serve in a bunch of capacities - you can use it as a good global store that you can monitor for changes (ie. Message Bus). Give it a try - give us feedback - and may all your apps be Shiny!

comments powered by Disqus