Adding Application Settings to a Unity-based HoloLens Application

Martin Tirion
6 min readAug 17, 2022

This article is part of a series with best practices from projects executed by a Microsoft CSE team I’m part of. Have a look at Learnings from developing a HoloLens Application to get the overview of the other posts.

Most applications need settings. These could be static settings which are set during the build/publish process, or it could be user settings that can be changed at runtime. This post describes a possible solution for both.

If you want to have a quick start using setting with this, we have made a Unity package available that will setup the basic structure with some extra functionality like reading settings, both static and user settings. If you want to use this package, you can download it here. All the sources can be found in the MRTK-Utilities repo.

Static settings

In a project I was working on, we needed secrets to connect securely to a backend API and Azure Services. There are various solutions for this, but we chose to have an appsettings.json file that is injected during the build/publish of the application.

Unity has the StreamingAssets folder to add files to the application that can be read from disk at runtime. To make sure that this folder is available when you build it, also when you’re doing that in a CI/CD pipeline, you can create this folder already in your project. To make sure that it’s committed to your repo add a dummy file StreamingAssets placeholder.txt in that folder.

You can place any type of file in here. We used a JSON structured file, as it is easy to read and convert using a JSON-parser. We decided to use JSON.NET, as the built-in JSON Converters in Unity have lots of limitations. This is an example appsettings.json we use:

{
"ClientId": "d7606020-15a1-46a1-9bd5-59939a986511",
"TenantId": "8b3e1766-2959-4aa8-bcb0-13c1bbeacb67",
"Scopes": "api://75185db7-1ac9-4aec-ba48-77a852106dd4/user_impersonation",
"Resource": "api://75185db7-1ac9-4aec-ba48-77a852106dd4",
"BaseEndPointUrl": "https://YOUR-API-ENDPOINT.azurewebsites.net"
}

When you clone the repo to your local machine, you can use this by copying a development version of this file for your project to the StreamingAssets folder. In a CI/CD pipeline this file can be generated from settings and added to the StreamingAssets folder.

Important: never place this file in the repo. Secrets should be kept outside of the source repository!

To easily read these settings in the application, we need a model class. For the sample above we created this model class.

public class AppSettings
{
public string ClientId;
public string TenantId;
public string Scopes;
public string Resource;
public string BaseEndPointUrl;
public bool IsValid()
{
return !string.IsNullOrEmpty(ClientId) &&
!string.IsNullOrEmpty(TenantId) &&
!string.IsNullOrEmpty(Scopes) &&
!string.IsNullOrEmpty(Resource) &&
!string.IsNullOrEmpty(BaseEndPointUrl);
}
}

Now let’s look at the code to read this file in the Unity app:

public AppSettings LoadSettings()
{
AppSettings settings = new AppSettings();
string path = Path.Combine(
UnityEngine.Application.streamingAssetsPath,
"appsettings.json");
try
{
Debug.Log($"Retrieving settings from {path}");
if (!File.Exists(path))
{
Debug.Log($"ERROR: settings not found.");
return null;
}
// load from disk and deserialize
string json = File.ReadAllText(path);
settings = JsonConvert.DeserializeObject<AppSettings>(json);
if (!settings.IsValid())
{
Debug.LogError($"ERROR: Settings are invalid.");
return null;
}
// log only first part of the secrets
Debug.Log($"Client: {settings.ClientId.Substring(0, 4)}***");
Debug.Log($"Tenant: {settings.TenantId.Substring(0, 4)}***");
Debug.Log($"Scopes: {settings.Scopes.Substring(0, 10)}***");
Debug.Log($"Res: {settings.Resource.Substring(0, 10)}***");
return settings;
}
catch (Exception ex)
{
Debug.LogError($"ERROR:\n{ex}");
return null;
}
}

To highlight a few pieces of this code:

  • We use UnityEngine.Application.streamingAssetsPath to get the path where the resource files are stored. Using Path.Combine ensures concatenating folder- and filenames is done in the right way for each platform.
  • We use JSON.NET to deserialize the JSON using our model class.
  • We have added a IsValid class to the model class to check that all settings are set. Other validation could be added here as well of course. We use this method here to validate if we have useable settings.
  • We log parts of the secrets which can be useful for development purposes. Just showing the first few characters is mostly enough to see if you’re working with the correct settings without giving away the complete secrets.

You can import the Unity package I created to setup the basic Application structure. The model class can be found in Assets\Application\Scripts\ Models\AppSettings.cs and the settings reading can be found in Assets\ Application\Scripts\SettingsManager.cs. This can also be found in the MRTK-Utilities repo.

User settings

If you want to have user settings, either used by internals of your application or exposed with an UI to the user, you can take the same approach as the static settings. The difference is that you should store it in a different place and that you also need code to write the settings file.

It’s important to understand that a HoloLens application is essentially a Universal Windows Platform (UWP) application. So it has to deal with the possibilities and restrictions of that platform.

A UWP app has access to special folders dedicated to the app and the user. Depending on how you want the files to be handled, you can choose locations like Local, Cache or Roaming. See Getting started storing app data locally and Save and load settings in a UWP app — UWP applications | Microsoft Docs for more information on the purpose of these folders.

Note: the special folders are dedicated to the app AND the user. So it’s not possible to use this to share settings between users. If you need something like that, you might need to setup an API to implement that.

Because the Unity Editor has a different environment from the running app on HoloLens, we use this piece of code to determine the root path depending on the environment we’re in:

private string _settingsPath;...#if WINDOWS_UWP
_settingsPath = Path.Combine(
ApplicationData.Current.LocalCacheFolder.Path,
"UserSettings");
#else
_settingsPath = Path.Combine(
Application.persistentDataPath,
_stateFileName);
#endif

So, for UWP we use the LocalCacheFolder. This can be found in %localappdata%\packages\AppData\Local\[package family name]\LocalCache. The package family name can be set when you create the package in Visual Studio in the Package.appxmanifest in the project marked with (Universal Windows). Open that file and navigate to the Packaging tab. The package family name is generated by using the Package name and a generate identifier.

For Unity we use the Application.persistentDataPath which translates to %userprofile%\AppData\LocalLow\[Company name]\[Product name]. The company name and product name are set in the Project Settings dialog in Unity on the Player tab.

Now we know where to store the settings file, we can use a model class again to structure the data. The model class can contain lists or other classes as well to have a more complicated structure. So we could have something like this:

using System;
using System.Collections.Generic;
public class Settings
{
public DateTime LastUpdate { get; set; }
public List<Contact> Contacts { get; set; }
}

To save data to a JSON formatted file, we can use this simple code:

public void SaveSettings(Settings settings)
{
string json = JsonConvert.SerializeObject(settings);
File.WriteAllText(_statePath, json);
}

To read the data from the file we have some more error handling and always return settings, although it might be initialized settings.

private Settings LoadSettings()
{
if (!File.Exists(_statePath))
{
return new Settings();
}
else
{
try
{
string json = File.ReadAllText(_statePath);
settings = JsonConvert.DeserializeObject<Settings>(json);
}
catch (Exception ex)
{
Debug.LogError($"ERROR: {ex}");
return new Settings();
}
}
}

Caching data for the user

The approach for saving user settings can also be used for caching data locally. Maybe you want to save and restore state of the application. Or cache the data in the application for faster (re)load or access. For all these approaches you can use this same mechanism.

Conclusion

To read and save all kinds of settings, being static settings, user settings or even cached data, you have to determine where to store it and how. Using a simple text file format with JSON content in combination with a library like JSON.NET makes it easy to do this.

In other posts we’ll explain how to use settings, for instance in authentication. See Learnings from developing a HoloLens Application for the overview of the posts.

--

--

Martin Tirion

Senior Software Engineer at Microsoft working on Azure Services and Spatial Computing for enterprise customers around the world.