I'm writing a .NET MAUI app and I would like to use Microsoft.Maui.Storage.Preferences in my ViewModel, however would prefer not to tie my ViewModel to the static Preferences class otherwise it will be hard to test. So I noticed there is an IPreferences interface, but I don't know what it's purpose is, as I can't inject a static implementation (and Preferences is static).
In my startup class I do the usual builder.Services.AddSingleton() and .AddTransient():
builder.Services
.AddSingleton<AppShell>()
.AddTransient<MyViewModel>()
...
But I can't do this with a static class. This won't work because you can't use a static class as a type argument:
.AddSingleton<IPreferences, Preferences>()
This doesn't work because it's not provided by the framework:
.AddSingleton<IPreferences>()
And I tried to get fancy:
.AddSingleton<IPreferences>(_ => Preferences.Default)
but I get this error when using it:
_preferences.Get("SomeKey", DateTime.Now); // error here, but _preferences is defined
[mono-rt] [ERROR] FATAL UNHANDLED EXCEPTION: System.NullReferenceException: Object reference not set to an instance of an object.
[mono-rt] at Microsoft.Maui.Storage.PreferencesImplementation.Get[DateTime](String key, DateTime defaultValue, String sharedName) in D:\a\_work\1\s\src\Essentials\src\Preferences\Preferences.android.cs:line 141
[mono-rt] at ... my filenames removed ... in ...\Shells\AppShell.xaml.cs:line 51
[mono-rt] at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__128_0(Object state)
[mono-rt] at Android.App.SyncContext.<>c__DisplayClass2_0.<Post>b__0() in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.App/SyncContext.cs:line 36
[mono-rt] at Java.Lang.Thread.RunnableImplementor.Run() in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Java.Lang/Thread.cs:line 36
[mono-rt] at Java.Lang.IRunnableInvoker.n_Run(IntPtr jnienv, IntPtr native__this) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/net6.0/android-31/mcw/Java.Lang.IRunnable.cs:line 84
[mono-rt] at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(_JniMarshal_PP_V callback, IntPtr jnienv, IntPtr klazz) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs:line 22
Can I inject IPreferences or do I have to create my own interface backed by the static Preferences?
IPreferences is for saving small key-value pairs. You can inject it referencing Preferences.Default.
And then use:
settings.Get("Language", 0);
Where settings is:
IPreferences settings;
What I like to do is make SettingsService, and then use:
public int Language
{
get => settings.Get("Language", 0);
set => settings.Set("Language", value);
}
There are other ways to handle your settings, but for few values, this works for me.
Edit(The code you requested.)
Injecting:
builder.Services.AddSingleton<ISettings>(new SettingsService(Preferences.Default));
ISettings interface:
public interface ISettings
{
int Language { get; set; }
}
SettingsService class:
internal class SettingsService : ISettings
{
IPreferences settings;
public SettingsService(IPreferences settings)
{
this.settings = settings;
}
public int Language
{
get => settings.Get("Language", 0);
set => settings.Set("Language", value);
}
}
Using in ViewModel:
public MyViewModel(ISettings settings)
{
lang = settings.Language;
}
Edit2: since you do not want to "manually construct". You can do this.
builder.Services.AddSingleton<IPreferences>(Preferences.Default);
builder.Services.AddSingleton<ISettings, SettingsService>();
With this you can use IPreferences, as well as ISettings. And SettingsService constructor will use the IPreferences injected here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With