xamarinbindingxamarin.formssettingsbindable

Xamarin Forms Settings Plugin Bindable


I've been the really great Xamarin Cross Platform settings plugin https://components.xamarin.com/view/SettingsPlugin (the Nuget version of this in a PCL to be exact)

It works well, but now using Xamarin forms i'd really like to be able to Bind to the settings values. I.e Id like to have a label on a content page that says: "The settings value is: [value]" and updates when the setting changes.

The settings class looks like this:

 public static class Settings {

   private static ISettings AppSettings
{
  get
  {
    return CrossSettings.Current;
  }
}

#region Setting Constants

    const string HOT_TIME_COUNT_KEY = "hotTimeCount";
    private static readonly int HOT_TIME_COUNT_DEFAULT = 0;



    #endregion



    public static int HotTimeCount
    {
        get { return AppSettings.GetValueOrDefault(HOT_TIME_COUNT_KEY, HOT_TIME_COUNT_DEFAULT); }
        set { AppSettings.AddOrUpdateValue(HOT_TIME_COUNT_KEY, value); }
    }

}

I just can't seem to work out what format is required to make this work? BindableProperty.Create ? Does it need an OnPropertyChanged method? Does the Settings Class have to derive from BindableObject?


Solution

  • I created this library :) You can always post an issue on the github, but essentially you need to create a view model with a public property that you wish to data bind to and then call into settings from there and raise a property changed notification if the value changed. Your Settings.cs can stay the same but you will need to create the viewmodel such as:

     public class MyViewModel : INotifyPropertyChanged
    {
    
        public int Time
        {
            get { return Settings.HotTimeCount; }
            set
            {
                if (Settings.HotTimeCount == value)
                    return;
    
                Settings.HotTimeCount = value;
                OnPropertyChanged();
            }
    
        }
    
        private Command increase;
        public Command IncreaseCommand
        {
            get 
            { 
                return increase ?? (increase = new Command(() =>Time++));
            }
        }
    
        #region INotifyPropertyChanged implementation
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        public void OnPropertyChanged([CallerMemberName]string name = "")
        {
            var changed = PropertyChanged;
            if (changed == null)
                return;
            changed(this, new PropertyChangedEventArgs(name));
        }
    
        #endregion
    
    
    }
    

    Then you XAML will look like this inside your Content page:

    <StackLayout Padding="25">
    
    <Button Text="Increase" Command="{Binding IncreaseCommand}"/>
    <Label Text="{Binding Time, StringFormat='The time is {0:F0}'}"/>
    
    </StackLayout>
    

    Make sure you set the BindingContext in the xaml.cs of the page:

    public partial class MyPage : ContentPage
    {
        public MyPage()
        {
            InitializeComponent();
            BindingContext = new MyViewModel();
        }
    }
    

    This actually isn't too much code to actually implement as your ViewModel would have a BaseViewModel that implements INotifyProprety changed, so really you are just adding in

     public int Time
        {
            get { return Settings.HotTimeCount; }
            set
            {
                if (Settings.HotTimeCount == value)
                    return;
    
                Settings.HotTimeCount = value;
                OnPropertyChanged();
            }
    
        }
    

    More magical way

    However, using the powers of C# and knowing how Databinding works you could first create a BaseViewModel that everything will use:

     public class BaseViewModel : INotifyPropertyChanged
    {
    
        public Settings Settings
        {
            get { return Settings.Current; }
        }
    
    
        #region INotifyPropertyChanged implementation
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        public void OnPropertyChanged([CallerMemberName]string name = "")
        {
            var changed = PropertyChanged;
            if (changed == null)
                return;
            changed(this, new PropertyChangedEventArgs(name));
        }
    
        #endregion
    
    
    }
    

    Notice my reference to Settings.Current, we will need to implement that now as a singleton, but we will use our BaseViewModel so we don't have to re-implement INotifyPropertyChanged:

    public class Settings : BaseViewModel
    {
        static ISettings AppSettings
        {
            get
            {
                return CrossSettings.Current;
            }
        }
    
        static Settings settings;
        public static Settings Current
        {
            get { return settings ?? (settings = new Settings()); }
        }
    
        #region Setting Constants
    
        const string HOT_TIME_COUNT_KEY = "hotTimeCount";
        static readonly int HOT_TIME_COUNT_DEFAULT = 0;
    
        #endregion
    
        public int HotTimeCount
        {
            get
            { 
                return AppSettings.GetValueOrDefault<int>(HOT_TIME_COUNT_KEY, HOT_TIME_COUNT_DEFAULT); 
            }
            set
            { 
                if (AppSettings.AddOrUpdateValue<int>(HOT_TIME_COUNT_KEY, value))
                    OnPropertyChanged();
    
            }
        }
    }
    

    Now of course we will still want to create a unique ViewModel that our XAML view will bind to:

     public class MyViewModel : BaseViewModel
    {
        private Command increase;
        public Command IncreaseCommand
        {
            get 
            { 
                return increase ?? (increase = new Command(() =>Settings.HotTimeCount++));
            }
        }
    }
    

    Notice that we are now inheriting from BaseViewModel, which means our command can actually just increment Settings.HotTimeCount! But now we must adjust our Xaml just a bit as to what we are actually data binding to for our label:

     <StackLayout Padding="25">
    
    <Button Text="Increase" Command="{Binding IncreaseCommand}"/>
    <Label BindingContext="{Binding Settings}" Text="{Binding HotTimeCount, StringFormat='The time is {0:F0}'}"/>
    
    </StackLayout>
    </ContentPage.Content>
    

    Notice I am setting the BindingContext to our Settings, which is in our BaseViewModel for the Label, this must be done because that is where it is located now. And there you have it.

    I will update my readme with this info.