Previous part
Introduction
Welcome back to the "Data Persistence in Xamarin.Forms" series. In the previous chapter, we saw how to save the user’s preferences. In this one, I’ll show you how to use file system to save data using Xamarin with the .Forms UI Technology and the PCL (Portable Class Library) code sharing strategy.
Before coding, just a little refresher about the Xamarin.Forms code sharing logic.
When we code in Xamarin.Forms environment, we’ve got to keep in mind that not all code can be sharable. Indeed, the following are not sharable and they need to be implemented with a platform-specific approach:
- Uses files and folders on the device (We are concerned about in this tutorial)
- Access system information
- Access personal information
- Uses external devices.
Also, we can use three methods to implement the write/read text using the file system:
To simplify the argument, in this tutorial, I’ll use only the text method.
Moreover, I’ll not show you how to manipulate saved data because that is out of the scope of the present tutorial. I’ll cover the manipulation data methods in a future article.
It’s important to say, before starting, that I’ll use “basic code method” and not the “clean code” one, because the purpose of the present tutorial is to show you the basics to save data.
Prerequisites
- IDE: Visual Studio 2017 Community Edition.
The steps given below will lead you to the goal.
Part 2
File System
Step 1
Launch Visual Studio and create a default application.
Step 2
Now, we need to customize the User Inteface, adding an Entry, a Button and a ListView. Please note that the ListView has been added to show you that the code works. To do that, go to Solution Explorer, expand the solution Portable node, open the MainPage.xaml file, and replace the auto-generated code with the given one.
- <?xml version="1.0" encoding="utf-8" ?>
- <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
- xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
- xmlns:local="clr-namespace:StoreWithFileSys"
- x:Class="StoreWithFileSys.MainPage">
-
-
- <ContentPage.Padding>
- <OnPlatform x:TypeArguments="Thickness" iOS="0,20,0,0" />
- </ContentPage.Padding>
-
- <StackLayout>
- <Entry x:Name="wroteText"
- Placeholder="Insert your text here"HorizontalTextAlignment="Center" />
- <Button x:Name="saveBtn" Text="Save" Clicked="saveBtn_Clicked" />
-
- <ListView x:Name="textsList">
- <ListView.ItemTemplate>
- <DataTemplate>
- <TextCell Text="{Binding}" />
- </DataTemplate>
- </ListView.ItemTemplate>
- </ListView>
- </StackLayout>
- </ContentPage>
Step 3
Now, we are going to connect the User Interface to the engine, in the Code Behind of the app. Our goal is to save the data written into the Entry field and show those in the ListView.
To do that, go to Solution Explorer, expand the solution Portable node, open the MainPage.xaml.cs file , delete the auto-generated code and write the following.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- using Xamarin.Forms;
- using StoreWithFileSys;
-
-
- namespace StoreWithFileSys
- {
- public partial class MainPage : ContentPage
- {
- FileEngine fileEngine = new FileEngine();
-
- public MainPage()
- {
- InitializeComponent();
- Refresh();
- }
-
- async void saveBtn_Clicked(object sender, EventArgs e)
- {
- string storedText = wroteText.Text;
- await fileEngine.WriteTextAsync(wroteText.Text, "");
- wroteText.Text = "";
- Refresh();
- }
-
- async void TextsList_Selected(object sender, SelectedItemChangedEventArgs e)
- {
- string storedText = (string)e.SelectedItem;
- wroteText.Text = storedText;
- }
-
- async void Refresh()
- {
- textsList.ItemsSource = await fileEngine.GetFilesAsync();
- textsList.SelectedItem = null;
- }
- }
- }
Step 4
Now we need to implement the engine to store our data. When we use the Xamarin.Forms Environment, to save data with File System we’ve got to use Dependency Service to have access to Input/Output functions, add an Interface in the PCL Project to define functions, implement the interface with a platform-specific approach for each OS and connect the entire code using a Class added in a PCL project. Let me show you the code:
Create an interface in PCL Project
(right click on the Portable Project -> Add -> New Item -> Code -> Interface) and write the following code,
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
-
- namespace StoreWithFileSys
- {
- public interface IFileEngine
- {
- Task WriteTextAsync(string storedText, string text);
- Task<string> ReadTextAsync(string storedText);
- Task<IEnumerable<string>> GetFilesAsync();
- }
- }
Implement the Interface for Android Engine
(right click on the Android Project -> Add -> New Item -> Visual C# ->Class) and write the following code,
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.IO;
- using Xamarin.Forms;
- using StoreWithFileSys;
- using System.Threading.Tasks;
-
- [assembly: Dependency(typeof(StoreWithFileSys.Droid.FileEngine))]
-
-
- namespace StoreWithFileSys.Droid
- {
- class FileEngine : IFileEngine
- {
- public Task<IEnumerable<string>> GetFilesAsync()
- {
- IEnumerable<string> storedTexts =
- from filePath in Directory.EnumerateFiles(DocsPath())
- select Path.GetFileName(filePath);
- return Task<IEnumerable<string>>.FromResult(storedTexts);
- }
-
- public async Task<string> ReadTextAsync(string storedText)
- {
- string filePath = FilePath(storedText);
- using (StreamReader reader = File.OpenText(filePath))
- {
- return await reader.ReadToEndAsync();
- }
- }
-
- public async Task WriteTextAsync(string storedText, string text)
- {
- string filePath = FilePath(storedText);
- using (StreamWriter writer = File.CreateText(filePath))
- {
- await writer.WriteAsync(text);
- }
- }
-
- private string FilePath(string storedText)
- {
- return Path.Combine(DocsPath(), storedText);
- }
-
- private string DocsPath()
- {
- return Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
- }
- }
- }
Implement the Interface for iOS Engine
(right click on the Android Project -> Add -> New Item -> Code ->Class) and write the following code,
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.IO;
- using Xamarin.Forms;
- using StoreWithFileSys;
- using System.Threading.Tasks;
-
- [assembly: Dependency(typeof(StoreWithFileSys.iOS.FileEngine))]
-
-
- namespace StoreWithFileSys.iOS
- {
- class FileEngine : IFileEngine
- {
- public Task<IEnumerable<string>> GetFilesAsync()
- {
- IEnumerable<string> storedTexts =
- from filePath in Directory.EnumerateFiles(DocsPath())
- select Path.GetFileName(filePath);
- return Task<IEnumerable<string>>.FromResult(storedTexts);
- }
-
- public async Task<string> ReadTextAsync(string storedText)
- {
- string filePath = FilePath(storedText);
- using (StreamReader reader = File.OpenText(filePath))
- {
- return await reader.ReadToEndAsync();
- }
- }
-
- public async Task WriteTextAsync(string storedText, string text)
- {
- string filePath = FilePath(storedText);
- using (StreamWriter writer = File.CreateText(filePath))
- {
- await writer.WriteAsync(text);
- }
- }
-
- private string FilePath(string storedText)
- {
- return Path.Combine(DocsPath(), storedText);
- }
-
- private string DocsPath()
- {
- return Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
- }
- }
- }
Implement the Interface for UWP Engine
- using System;
- using System.Collections.Generic;
- using System.Threading.Tasks;
- using Xamarin.Forms;
- using Windows.Storage;
- using System.Linq;
-
- [assembly: Dependency(typeof(StoreWithFileSys.UWP.FileEngine))]
-
- namespace StoreWithFileSys.UWP
- {
- class FileEngine : IFileEngine
- {
-
- public async Task WriteTextAsync(string storedText, string text)
- {
- StorageFolder userLocalFolder = ApplicationData.Current.LocalFolder;
- IStorageFile userStorageFile = await userLocalFolder.CreateFileAsync(storedText, CreationCollisionOption.ReplaceExisting);
- await FileIO.WriteTextAsync(userStorageFile, text);
- }
-
- public async Task<string> ReadTextAsync(string storedText)
- {
- StorageFolder userLocalFolder = ApplicationData.Current.LocalFolder;
- IStorageFile userStorageFile = await userLocalFolder.GetFileAsync(storedText);
- return await FileIO.ReadTextAsync(userStorageFile);
- }
-
- public async Task<IEnumerable<string>> GetFilesAsync()
- {
- StorageFolder userLocalFolder = ApplicationData.Current.LocalFolder;
- IEnumerable<string> storedTexts =
- from userStorageFile in await userLocalFolder.GetFilesAsync()
- select userStorageFile.Name;
- return storedTexts;
- }
- }
- }
Create a class to connect the entire code
(right click on the PCL Project -> Add -> New Item -> Code ->Class) and write the following code,
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- using System.IO;
- using Xamarin.Forms;
-
- namespace StoreWithFileSys
- {
- class FileEngine : IFileEngine
- {
- IFileEngine fileEngine = DependencyService.Get<IFileEngine>();
-
-
- public Task<IEnumerable<string>> GetFilesAsync()
- {
- return fileEngine.GetFilesAsync();
- }
-
- public Task<string> ReadTextAsync(string storedText)
- {
- return fileEngine.ReadTextAsync(storedText);
- }
-
- public Task WriteTextAsync(string storedText, string text)
- {
- return fileEngine.WriteTextAsync(storedText, text);
- }
- }
- }
Step 5
Now, we’ll see the output on the three mobile platforms, on Windows desktop, and the relative behavior within:
Windows 10 Desktop
Windows 10 mobile
iOS
Android
Conclusions
In this tutorial I have shown you how to save data using the OS File System.
As you saw, the code above contains the “async method”. That’s important to avoid the block of the user interface during the data saving process. Indeed, the UWP data saving method must be asyncronus. Moreover, it’s a bit complicated to keep in mind that the data saving code is not sharable and needs the platform-specific implementation.
The “roadmap” of a build like that is the following
- Build User Interface: -> Step n.2: to interact with the user.
- Build Code Behind -> Step n. 3: to connect the UI to the engine of the app.
- Build Interface -> Step n. 4: to say to the program what to do, but not how.
- Implement interface for each concerned OS platform -> Step n. 4: because, as I said before, saving data process must be implemented with the platform-specific approach.
- Build the class to unify the entire code -> Step n. 4: to allow the parts of the program to work together.
Of course, you can improve it, but that is out of our scope at this time.
Just another thing: the code shown above has an instructive purpose, but, if you are seaching for a easier way, you can use the PCLStorage plugin by Daniel Plaisted ( https://www.nuget.org/packages/PCLStorage/1.0.2/ ). You can find it in NuGet: PCL Storage provides a consistent, portable set of local file IO APIs for .NET, Windows Phone, Windows Store, Xamarin.iOS, Xamarin.Android, and Silverlight. This makes it easier to create cross-platform .NET libraries and apps.
I’ll cover this solution in a future tutorial, while, in the next one, I’ll show you how to save data in a database.
Thank you for your attention and interest