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
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);
The value "TestValue" should be stored permanently in WPF.
On every restart of the program, the valus is gone.
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.
In WPF project create class, copy-pasting it from the above link and applying the fix
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
{
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>();
}
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