File Handling in Xamarin.Forms

File handling with Xamarin.Forms can be achieved using code in a .NET Standard library, or by using embedded resources.

Overview

Xamarin.Forms code runs on multiple platforms - each of which has its own filesystem. Previously, this meant that that reading and writing files was most easily performed using the native file APIs on each platform. Alternatively, embedded resources are a simpler solution to distribute data files with an app. However, with .NET Standard 2.0 it's possible to share file access code in .NET Standard libraries.

For information on handling image files, refer to the Working with Images page.

Saving and Loading Files

The System.IO classes can be used to access the file system on each platform. The File class lets you create, delete, and read files, and the Directory class allows you to create, delete, or enumerate the contents of directories. You can also use the Stream subclasses, which can provide a greater degree of control over file operations (such as compression or position search within a file).

A text file can be written using the File.WriteAllText method:

File.WriteAllText(fileName, text);

A text file can be read using the File.ReadAllText method:

string text = File.ReadAllText(fileName);

In addition, the File.Exists method determines whether the specified file exists:

bool doesExist = File.Exists(fileName);

The path of the file on each platform can be determined from a .NET Standard library by using a value of the Environment.SpecialFolder enumeration as the first argument to the Environment.GetFolderPath method. This can then be combined with a filename with the Path.Combine method:

string fileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "temp.txt");

These operations are demonstrated in the sample app, which includes a page that saves and loads text:

Saving and loading text

Loading Files Embedded as Resources

To embed a file into a .NET Standard assembly, create or add a file and ensure that Build Action: EmbeddedResource.

GetManifestResourceStream is used to access the embedded file using its Resource ID. By default the resource ID is the filename prefixed with the default namespace for the project it is embedded in - in this case the assembly is WorkingWithFiles and the filename is PCLTextResource.txt, so the resource ID is WorkingWithFiles.PCLTextResource.txt.

var assembly = IntrospectionExtensions.GetTypeInfo(typeof(LoadResourceText)).Assembly;
Stream stream = assembly.GetManifestResourceStream("WorkingWithFiles.PCLTextResource.txt");
string text = "";
using (var reader = new System.IO.StreamReader (stream)) {
    text = reader.ReadToEnd ();
}

The text variable can then be used to display the text or otherwise use it in code. This screenshot of the sample app shows the text rendered in a Label control.

Text file embedded in PCL

Loading and deserializing an XML is equally simple. The following code shows an XML file being loaded and deserialized from a resource, then bound to a ListView for display. The XML file contains an array of Monkey objects (the class is defined in the sample code).

var assembly = IntrospectionExtensions.GetTypeInfo(typeof(LoadResourceText)).Assembly;
Stream stream = assembly.GetManifestResourceStream("WorkingWithFiles.PCLXmlResource.xml");
List<Monkey> monkeys;
using (var reader = new System.IO.StreamReader (stream)) {
    var serializer = new XmlSerializer(typeof(List<Monkey>));
    monkeys = (List<Monkey>)serializer.Deserialize(reader);
}
var listView = new ListView ();
listView.ItemsSource = monkeys;

Xml file embedded in PCL, displayed in ListView

Embedding in Shared Projects

Shared Projects can also contain files as embedded resources, however because the contents of a Shared Project are compiled into the referencing projects, the prefix used for embedded file resource IDs can change. This means the resource ID for each embedded file may be different for each platform.

There are two solutions to this issue with Shared Projects:

  • Synchronize the Projects - Edit the project properties for each platform to use the same assembly name and default namespace. This value can then be "hardcoded" as the prefix for embedded resource IDs in the Shared Project.
  • #if compiler directives - Use compiler directives to set the correct resource ID prefix and use that value to dynamically construct the correct resource ID.

Code illustrating the second option is shown below. Compiler directives are used to select the hardcoded resource prefix (which is normally the same as the default namespace for the referencing project). The resourcePrefix variable is then used to create a valid resource ID by concatenating it with the embedded resource filename.

#if __IOS__
var resourcePrefix = "WorkingWithFiles.iOS.";
#endif
#if __ANDROID__
var resourcePrefix = "WorkingWithFiles.Droid.";
#endif

Debug.WriteLine("Using this resource prefix: " + resourcePrefix);
// note that the prefix includes the trailing period '.' that is required
var assembly = IntrospectionExtensions.GetTypeInfo(typeof(SharedPage)).Assembly;
Stream stream = assembly.GetManifestResourceStream
    (resourcePrefix + "SharedTextResource.txt");

Organizing Resources

The above examples assume that the file is embedded in the root of the .NET Standard library project, in which case the resource ID is of the form Namespace.Filename.Extension, such as WorkingWithFiles.PCLTextResource.txt and WorkingWithFiles.iOS.SharedTextResource.txt.

It is possible to organize embedded resources in folders. When an embedded resource is placed in a folder, the folder name becomes part of the resource ID (separated by periods), so that the resource ID format becomes Namespace.Folder.Filename.Extension. Placing the files used in the sample app into a folder MyFolder would make the corresponding resource IDs WorkingWithFiles.MyFolder.PCLTextResource.txt and WorkingWithFiles.iOS.MyFolder.SharedTextResource.txt.

Debugging Embedded Resources

Because it is sometimes difficult to understand why a particular resource isn't being loaded, the following debug code can be added temporarily to an application to help confirm the resources are correctly configured. It will output all known resources embedded in the given assembly to the Errors pad to help debug resource loading issues.

using System.Reflection;
// ...
// use for debugging, not in released app code!
var assembly = IntrospectionExtensions.GetTypeInfo(typeof(SharedPage)).Assembly;
foreach (var res in assembly.GetManifestResourceNames()) {
    System.Diagnostics.Debug.WriteLine("found resource: " + res);
}

Summary

This article has shown some simple file operations for saving and loading text on the device, and for loading embedded resources. With .NET Standard 2.0 it's possible to share file access code in .NET Standard libraries.