CHAPTER 3
Storing user preferences and app settings locally are very common requirements with cross-platform projects. There are tons of examples, including storing the last access date/time, the consent to use a cellular network when wi-fi is not available, color scheme preferences, and so on. These options do not generally need to be secured, but sometimes you might need to store an encrypted password or information that identifies the user. In this case, you do need to secure both preferences and settings.
The .NET MAUI code base provides types that make it extremely easy to store local settings. This chapter describes both scenarios and provides details on the classes, and their methods, you can use for these purposes. You can use the solution available inside the Chapter2\LocalSettings folder of the companion code, or you can create a new .NET MAUI project from scratch. If you do so, remove all the code generated by Visual Studio 2022: the entire ScrollView node in the MainPage.xaml file and the OnCounterClicked event handler in its code-behind file.
Local preferences in .NET MAUI can be easily managed via the Preferences class from the Microsoft.Maui.Storage namespace. The Preferences class stores preferences in the native local storages, more specifically:
· For Android, into Shared Preferences.
· For iOS, into NSUserDefault.
· For UWP, into the application data container.
On each platform, preferences are key/value pairs. The value can be of one of the primitive .NET types. Before understanding how they work, it is important to know the methods exposed by the Preferences class, summarized in Table 3.
Table 3: Methods exposed by the Preferences class
Methods exposed by the Preferences class | |
|---|---|
Set | Adds a preference to the local settings. |
Get | Gets a preference value from the local settings. |
Remove | Removes the specified preference. |
Clear | Deletes all the preferences for the current app. |
Default.ContainsKey | Detects if the specified preference exists in the storage given its key. |
Tip: Uninstalling an app will also remove all local preferences.
Let’s now start with an example. Suppose you want to store the last date/time the app has been used. You can do this in the OnSleep method of the App.xaml.cs file as follows:
protected override void OnSleep()
{
Preferences.Set("TimeOfLastUsage", DateTime.Now);
}
TimeOfLastUsage is the key, and the value is a DateTime object. Keys are always of type string. Retrieving a value from the local settings is still easy. In the OnStart method, you can write something like the following:
internal static DateTime timeOfLastUsage;
protected override void OnStart()
{
timeOfLastUsage = Preferences.Get("TimeOfLastUsage",
DateTime.Now);
}
The generic Get method returns the value for the specified key and stores the result in a variable. Get also requires you to specify a default value in case the key does not exist in the storage, and this is true for all the supported data types. In this example, local preferences are used to store information that can be useful for the app logic at runtime, so it’s not really a user preference.
Let’s make another example for this purpose: checking if the phone is connected to a cellular network only, asking the user permission to use it, and storing the result. Code Listing 6 contains the code that you want to add to the MainPage.xaml.cs file.
Code Listing 6
private bool CheckCellularConnection() { var profiles = Connectivity.ConnectionProfiles; return profiles.Count() == 1 && profiles.Contains(ConnectionProfile.Cellular); } private bool useCellularNetwork; protected override async void OnAppearing() { if(CheckCellularConnection()) { if (!Preferences.ContainsKey("UseCellularNetwork")) { bool result = await DisplayAlert("Warning", "Do you agree on using cellular data when Wi-Fi is not available?", "Yes", "No"); Preferences.Set("UseCellularNetwork", result); useCellularNetwork = result; } else useCellularNetwork = Preferences.Get("UseCellularNetwork", false); } } |
The CheckCellularConnection method checks if only one connection is available, and if this is a cellular network. The remaining code is in the OnAppearing method for convenience so that the code is executed at the app startup, but you will likely create a separate method that will be invoked when the ConnectivityChanged event of the Connectivity class is raised.
In this second example, the code invokes the Preferences.ContainsKey method to detect if the preference was already saved, then it still invokes the Set and Get methods to store and retrieve the preference value, respectively. The Preferences class is intended to store small pieces of information represented by primitive .NET types. If you need to save and retrieve more complex and structured data locally, your best option will be an SQLite database, as discussed in Chapter 4.
Sometimes you need to store information locally, but you also need the information to be secured. Each of the supported operating systems offer a secure place to store encrypted information, and the .NET MAUI library provides the SecureStorage class, which exposes methods to work with secure storages in a cross-platform way.
There are a few differences with nonsecured preferences discussed in the previous section:
· While preferences are stored inside the app’s own cache, the secure storage can be accessed by multiple applications.
· As an implication, data you save in the secure storage is not removed when you uninstall an app. This is extremely important to consider.
· You still work with key/value pairs, but the value can be of type string only.
The native implementation for the secure storage can be summarized as follows:
· For iOS, it is located in the KeyChain.
· For Android, it is located in the KeyStore.
· For UWP, data is encrypted with the DataProtectionProvider algorithms.
When working with iOS simulators, you will need to take a few more steps to set up secure access; otherwise, you will get an exception. This is explained in the next paragraph.
If you want to test and debug your code on iOS simulators, you need to explicitly enable the KeyChain in Visual Studio. This is accomplished by editing the Entitlements.plist file. However, unlike with Xamarin.Forms, in .NET MAUI this file is not added by default when the solution is generated. So, manually add a new XML file called Entitlements.plist to the Platforms\iOS subfolder of the project and add the following XML root node:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
</dict>
</plist>
Save the file and close the code editor. At this point, follow these steps:
These entitlements should be removed when working on physical devices and before distribution.
The biggest benefit of the secure storage is that it automatically encrypts the information with the highest levels of security possible offered by each operating system. The Microsoft.Maui.Storage.SecureStorage class exposes two simple methods: SetAsync and GetAsync. The first method saves the specified key and value, and the second method retrieves the value for the specified key. If the key does not exist, it returns null, but you cannot specify a default value like you do with local preferences.
To understand how the class works, suppose you have the following user interface where the user can enter a password:
<VerticalStackLayout VerticalOptions="CenterAndExpand"
HorizontalOptions="FillAndExpand">
<Label Text="Enter your password: "
FontSize="Large"
Margin="0,20,0,0"/>
<Entry IsPassword="True"
x:Name="PasswordEntry"
Margin="0,10,0,0"/>
<Button Text="OK" Margin="50,20,50,0" x:Name="OkButton"
Clicked="OkButton_Clicked"/>
</VerticalStackLayout>
The Clicked event handler for the button contains the code that stores the password in the local storage:
private async void OkButton_Clicked(object sender, System.EventArgs e)
{
// Perform password validation here...
await SecureStorage.SetAsync("Password", PasswordEntry.Text);
}
Obviously, you will implement your logic for password validation, but for the sake of simplicity, the code stores the password directly. The key points are the two string parameters, key and value, for the SetAsync method, and the usage of the await operator, as this is an asynchronous call. The code you need to write to retrieve information from the secure storage looks like the following, related to the current example:
// Assuming you have declared a bool field called hasPassword
string password = await SecureStorage.GetAsync("Password");
if (password != null)
hasPassword = true;
GetAsync returns the value for the specified key if the key exists in the secure storage. Otherwise, it returns null, so a null check is necessary to avoid exceptions. As you can see, reading and writing data against the secure storage is very straightforward, though I recommend you use this feature only with really sensitive information.
As I mentioned previously, when you uninstall an app that stores information in the secure storage, such information is not removed because the secure storage is at the operating system level, not at the app level (though the OS knows what app is associated with the key). You certainly cannot control when an app is uninstalled, but if the app is running on the device for the first time (which includes a reinstall), you can check whether a key already exists in the secure storage, so that it can be removed and a fresh environment provided to the user. The following method demonstrates how to accomplish this:
private async Task CheckAppFirstRun()
{
if(VersionTracking.IsFirstLaunchEver)
{
string password = await SecureStorage.GetAsync("password");
if (password != null)
SecureStorage.Remove("password");
}
}
The VersionTracking class from the Microsoft.Maui.ApplicationModel namespace provides properties and methods that help you work with the app’s versions and builds. IsFirstLaunchEver returns true if the app is running for the first time on the device. The SecureStorage.Remove method deletes the key and its value from the secure storage. You can avoid this code if you instead want to reuse the value of an existing key.
Note: Remember to invoke VersionTracking.Track the very first time the app runs; otherwise, the class will not be able to generate version information.
Storing preferences and user settings is a very frequent task in mobile applications, and the .NET MAUI library provides the Preferences class for it, which allows for saving key/value pairs in the app’s local storage. Supported data types are .NET primitive types. If the information you need to store locally requires encryption, you can leverage the secure storage associated with each OS, for which .NET MAUI offers the SecureStorage class, which supports key/value pairs of type string.
In the next chapter, you will see a very practical example about using the secure storage, which is the implementation of biometric authentication.