Xamarin.forms: Local store not persistant when restarting App in Xamarin Forms WPF

Created on 10 Aug 2018  路  7Comments  路  Source: xamarin/Xamarin.Forms

Description

Trying to store local data permanently in Xamarin Forms WPF, but as soon as we stop the program and then restart, all the settings are gone. This works correctly in UWP and Android

Steps to Reproduce

  1. Create a new WPF project.
  2. Add the following code to the MainWindow class:

        object Test = null;
        bool found = Application.Current.Properties.TryGetValue("Test",out Test);
        Application.Current.Properties["Test"]= "TestValue";
        Application.Current.SavePropertiesAsync();
        found = Application.Current.Properties.TryGetValue("Test", out Test);
    
  3. Start the program (the value should be stored permanently)
  4. End the program, restart and check the value Application.Current.Properties.Count in debug mode. It is 0, but should be 1.

Expected Behavior

The value "TestValue" should be stored permanently in WPF.

Actual Behavior

On every restart of the program, the valus is gone.

Basic Information

  • Version with issue: VS2017 Ver. 15.7.6
  • Last known good version:
  • IDE:
  • Platform Target Frameworks: WPF
  • Affected Devices: We have got the issue on two different Win 10 devices.
WPF bug

All 7 comments

Do you need to await Application.Current.SavePropertiesAsync() ?

Awaiting does not change anything

Same problem for me. I've narrowed it down to Properties collection serialization and saving to local storage. If the serialized string gets shorter, upon update to local storage file, there's trailing garbage at the end of file, which doesn't get truncated. Windows 10 x64, Xamarin.Forms.Platform.WPF 3.2.0.729530-pre2

E.g., bellow is the Properties data saved to local storage (default values upon first start):
<ArrayOfKeyValueOfstringanyType xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <KeyValueOfstringanyType> <Key>_Locale</Key> <Value i:type="a:string" xmlns:a="http://www.w3.org/2001/XMLSchema">en</Value> </KeyValueOfstringanyType> <KeyValueOfstringanyType> <Key>FileSizeGb</Key> <Value i:type="a:int" xmlns:a="http://www.w3.org/2001/XMLSchema">2</Value> </KeyValueOfstringanyType> <KeyValueOfstringanyType> <Key>WriteBuffering</Key> <Value i:type="a:string" xmlns:a="http://www.w3.org/2001/XMLSchema">false</Value> </KeyValueOfstringanyType> <KeyValueOfstringanyType> <Key>MemCache</Key> <Value i:type="a:string" xmlns:a="http://www.w3.org/2001/XMLSchema">false</Value> </KeyValueOfstringanyType> </ArrayOfKeyValueOfstringanyType>

Now look what happens to the file when 2 values are changed from 'false' to 'true', the resulting serialized string gets 2 chars shorter and yet there're 2 trailing ">" symbols remaining in the file (one symbol per each field update, each field update issues SavePropertiesAsync call):
<ArrayOfKeyValueOfstringanyType xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <KeyValueOfstringanyType> <Key>_Locale</Key> <Value i:type="a:string" xmlns:a="http://www.w3.org/2001/XMLSchema">ru</Value> </KeyValueOfstringanyType> <KeyValueOfstringanyType> <Key>FileSizeGb</Key> <Value i:type="a:int" xmlns:a="http://www.w3.org/2001/XMLSchema">2</Value> </KeyValueOfstringanyType> <KeyValueOfstringanyType> <Key>WriteBuffering</Key> <Value i:type="a:string" xmlns:a="http://www.w3.org/2001/XMLSchema">true</Value> </KeyValueOfstringanyType> <KeyValueOfstringanyType> <Key>MemCache</Key> <Value i:type="a:string" xmlns:a="http://www.w3.org/2001/XMLSchema">true</Value> </KeyValueOfstringanyType> </ArrayOfKeyValueOfstringanyType>>> - see that instead of single ">" closing tag ther're 3.

And here is the code for saving properties:

        private const string True = "true";
        private const string False = "false";

        public string MemCache
        {
            get
            {
                if (!Application.Current.Properties.ContainsKey(nameof(MemCache))) Application.Current.Properties[nameof(MemCache)] = False;
                return Application.Current.Properties[nameof(MemCache)] as string;
            }
            set
            {
                if (string.IsNullOrEmpty(value)) value = False;

                if (value != False && value != True) throw new InvalidOperationException("Cant set MemCache to: "+value);
                Application.Current.Properties[nameof(MemCache)] = value;
                Application.Current.SavePropertiesAsync();
                RaisePropertyChanged();
            }
        }

Found issue and fix.
https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Platform.WPF/Deserializer.cs

Line 51:
using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(PropertyStoreFile, FileMode.OpenOrCreate, isoStore))

Change to:
using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(PropertyStoreFile, FileMode.Create, isoStore))

Workaround.

  1. In WPF project create class, copy-pasting it from the above link and applying the fix

  2. Annotate the class/namespace with [Dependency] attribute


using System;
using System.Collections.Generic;
using System.IO;
using System.IO.IsolatedStorage;
using System.Runtime.Serialization;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Internals;

[assembly: Dependency(typeof(Saplin.CPDT.WPF.Deserializer))]
namespace Saplin.CPDT.WPF
{
    public class Deserializer : IDeserializer
    {
  1. Do some neurosurgery by swapping WPF serializer in DependencyService with your custom one
        private static void SawapDeserializer()
        {
            var des = Xamarin.Forms.DependencyService.Get<IDeserializer>();

            var type = typeof(Xamarin.Forms.DependencyService);
            var field = type.GetField("DependencyTypes", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
            var list = field.GetValue(null) as List<Type>;

            Type td = null;

            foreach (var t in list)
                if (t.FullName == "Xamarin.Forms.Platform.WPF.Deserializer")
                {
                    td = t;
                    break;
                }

            list.Remove(td);

            field = type.GetField("DependencyImplementations", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);

            var obj = field.GetValue(null);
            type = obj.GetType();
            var method = type.GetMethod("Clear");
            method.Invoke(obj, null);

            des = Xamarin.Forms.DependencyService.Get<IDeserializer>();
        }
  1. Call the method in WPF project's MainPage's code-behind right after Froms.Init method, e.g.:
    public partial class MainWindow : FormsApplicationPage
    {
        public MainWindow()
        {
            instance = this;

            InitializeComponent();
            Forms.Init();

            SawapDeserializer();

            LoadApplication(new Saplin.CPDT.UICore.App());
        }

FYI the exact same problem (trailing >>) was reported here (not in Xamarin of course)

IMHO, not related at all.

In this case Stream works as designed. OpenOrCreate flag assumes that nothing happens to existing file upon opening. Create flag always clears file contents.

The logic in Xamarin WPF serializer doesn't assume that serialized string might happen to be shorter than the previously serialized string.

With OpenOrCreate flag the issue is as follows:
Step 1 - persist 10 bytes to file, file size is 10 bytes
Step 2 - persist 8 bytes to file, file size is 10 bytes
Step 3 - load 10 bytes, break deserializer by unexpected 2 bytes

With Create flag:
Step 1 - persist 10 bytes to file, file size is 10 bytes
Step 2 - persist 8 bytes to file, file size is 8 bytes
Step 3 - load 8 bytes, deserialize valid 8 bytes

Was this page helpful?
0 / 5 - 0 ratings