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.ShinyStartup

{

    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