locked
why is this setting not being initialized? RRS feed

  • Question

  • I have a class that I've migrated and modified from WP8 to Win8 that is just an easy way for me to get to settings. However, I'm running into a situation to where nothing is being initialized, except the ones that are ints. It's not that the value property is null. The Setting itself is null. I never had this issue on wp8.

    The instance of the setting is a static setting in a static Settings class.

    public static RoamingSetting<List<string>> StoreItems = new RoamingSetting<List<string>>("StoreItems", new List<string>());


    Here's how I access the setting:

    if(MyAppLibrary.Settings.Settings.StoreItems.Value.Contains("FullVersion")) 
         return false;
    Here's the settings class
    public class RoamingSetting<T>
        {
            string name;
            T value;
            T defaultValue;
            bool hasValue;
            Type type;
    
            public RoamingSetting(string name, T defaultValue)
            {
                this.name = name;
                this.defaultValue = defaultValue;
                this.type = defaultValue.GetType();
            }
    
            public T Value
            {
                get 
                {
                    // check for cached value
                    if (!this.hasValue)
                    { 
                        // try to get the value from roaming settings
                        var value = Windows.Storage.ApplicationData.Current.RoamingSettings.Values[name];
                        if (value == null)
                        { 
                            // it hasn't been set yet
                            this.value = this.defaultValue;
                            try
                            {
                                Windows.Storage.ApplicationData.Current.RoamingSettings.Values[name] = this.defaultValue;
                            }
                            catch
                            {
                                // a not supported exception. doesn't seem to like the list<Word>.
                                // trying other code
                                value = LoadData();
                            }
                        }
                        this.hasValue = true;
                    }
                    return this.value;
                }
                set
                { 
                    // save to Roaming Settings
                    try
                    {
                        Windows.Storage.ApplicationData.Current.RoamingSettings.Values[this.name] = value;
                    }
                    catch
                    {
                        // this is required for collections
                        SaveData();
                    }
                    this.value = value;
                    this.hasValue = true;
                }
            }
    
            public T DefaultValue
            {
                get { return this.defaultValue; }
            }
    
            // "Clear" cached value
            public void ForceRefresh()
            {
                this.hasValue = false;
            }
    
            public async void SaveData()
            {
                StorageFile file = await ApplicationData.Current.RoamingFolder.CreateFileAsync(this.name, CreationCollisionOption.ReplaceExisting);
                using (IRandomAccessStream raStream = await file.OpenAsync(FileAccessMode.ReadWrite))
                {
                    using (IOutputStream outStream = raStream.GetOutputStreamAt(0))
                    {
                        DataContractSerializer serializer = new DataContractSerializer(type);
                        serializer.WriteObject(outStream.AsStreamForWrite(), this.value);
                        await outStream.FlushAsync();
                    }
                }
            }
    
            public async Task<T> LoadData()
            {
                T data = default(T);
                try
                {
                    StorageFile file;
                    
                    try
                    {
                        file = await ApplicationData.Current.RoamingFolder.GetFileAsync(this.name);
                    }
                    catch
                    {
                        file = null;
                    }
    
                    if (file == null)
                    {
                        this.value = this.defaultValue;
                        return this.value;
                    }
    
    
                    using (IInputStream inStream = await file.OpenSequentialReadAsync())
                    {
                        DataContractSerializer serializer = new DataContractSerializer(type);
                        data = (T)serializer.ReadObject(inStream.AsStreamForRead());
                    }
                }
                catch //(FileNotFoundException ex)
                {
                    // throw ex;
                    this.value = this.defaultValue;
                    return this.value;
                }
                return data;
    
            }
        }



    Michael DiLeo


    • Edited by mcd023 Friday, January 10, 2014 11:33 PM
    Friday, January 10, 2014 11:30 PM

Answers

  • You don't ever save the values in the code. You get the default empty list and fill it, but never save anything beyond that.

    When you first request the Value the code goes into LoadData which doesn't have a file to load and so generates a default List<string>. When that List<string> is filled it doesn't get saved.

    Another issue that may cause problems is that in your getter for Value you call the async LoadData call, but don't await it (you cannot await in the property getter). There is a race condition and it is non-deterministic if LoadData will set this.value before or after Value returns.

    For the first you can replace List<String> with ObservableCollection<String> and handle the CollectionChanged event to save the changes out.

    For the second, add an awaitable initializer to load the settings from your custom file rather than doing it inside the property getter itself.

    Better would be to use an ApplicationDataCompositeValue rather than rolling your own composite.

    • Marked as answer by mcd023 Saturday, January 11, 2014 5:48 AM
    Saturday, January 11, 2014 2:50 AM
    Moderator
  • Thanks a bunch! I managed to fix the issue. One was that I had a static settings class in a Portable Class Library. Moving the setting into the actual project solved that issue. Here's what the new class looks like.

    public class RoamingSetting<T>
        {
            string name;
            T value;
            T defaultValue;
            bool hasValue;

            public RoamingSetting(string name, T defaultValue)
            {
                this.name = name;
                this.defaultValue = defaultValue;
            }

            public T Value
            {
                get
                {
                    // check for cached value
                    if (!this.hasValue)
                    {
                        // create a composite setting
                        Windows.Storage.ApplicationDataCompositeValue composite = new ApplicationDataCompositeValue();
                        object o;
                        if (!composite.TryGetValue(this.name, out o))
                        {
                            // it hasn't been set yet
                            this.value = this.defaultValue;
                            ApplicationData.Current.RoamingSettings.Values[this.name] = composite;
                        }
                        else
                            this.value = (T)o;
                        this.hasValue = true;
                    }
                    return this.value;
                }
                set
                {
                    Windows.Storage.ApplicationDataCompositeValue composite = new ApplicationDataCompositeValue();
                    this.value = value;
                    this.hasValue = true;
                }
            }

            public T DefaultValue
            {
                get { return this.defaultValue; }
            }

            // "Clear" cached value
            public void ForceRefresh()
            {
                this.hasValue = false;
            }

    }


    Michael DiLeo


    • Marked as answer by mcd023 Saturday, January 11, 2014 5:48 AM
    • Edited by mcd023 Saturday, January 11, 2014 5:49 AM
    Saturday, January 11, 2014 5:47 AM

All replies

  • You don't ever save the values in the code. You get the default empty list and fill it, but never save anything beyond that.

    When you first request the Value the code goes into LoadData which doesn't have a file to load and so generates a default List<string>. When that List<string> is filled it doesn't get saved.

    Another issue that may cause problems is that in your getter for Value you call the async LoadData call, but don't await it (you cannot await in the property getter). There is a race condition and it is non-deterministic if LoadData will set this.value before or after Value returns.

    For the first you can replace List<String> with ObservableCollection<String> and handle the CollectionChanged event to save the changes out.

    For the second, add an awaitable initializer to load the settings from your custom file rather than doing it inside the property getter itself.

    Better would be to use an ApplicationDataCompositeValue rather than rolling your own composite.

    • Marked as answer by mcd023 Saturday, January 11, 2014 5:48 AM
    Saturday, January 11, 2014 2:50 AM
    Moderator
  • Thanks a bunch! I managed to fix the issue. One was that I had a static settings class in a Portable Class Library. Moving the setting into the actual project solved that issue. Here's what the new class looks like.

    public class RoamingSetting<T>
        {
            string name;
            T value;
            T defaultValue;
            bool hasValue;

            public RoamingSetting(string name, T defaultValue)
            {
                this.name = name;
                this.defaultValue = defaultValue;
            }

            public T Value
            {
                get
                {
                    // check for cached value
                    if (!this.hasValue)
                    {
                        // create a composite setting
                        Windows.Storage.ApplicationDataCompositeValue composite = new ApplicationDataCompositeValue();
                        object o;
                        if (!composite.TryGetValue(this.name, out o))
                        {
                            // it hasn't been set yet
                            this.value = this.defaultValue;
                            ApplicationData.Current.RoamingSettings.Values[this.name] = composite;
                        }
                        else
                            this.value = (T)o;
                        this.hasValue = true;
                    }
                    return this.value;
                }
                set
                {
                    Windows.Storage.ApplicationDataCompositeValue composite = new ApplicationDataCompositeValue();
                    this.value = value;
                    this.hasValue = true;
                }
            }

            public T DefaultValue
            {
                get { return this.defaultValue; }
            }

            // "Clear" cached value
            public void ForceRefresh()
            {
                this.hasValue = false;
            }

    }


    Michael DiLeo


    • Marked as answer by mcd023 Saturday, January 11, 2014 5:48 AM
    • Edited by mcd023 Saturday, January 11, 2014 5:49 AM
    Saturday, January 11, 2014 5:47 AM