Xamarin.Forms でアプリの設定情報を保存するには (Json.NET 使用)


こんにちは。エクセルソフトの田淵です。

> この記事は [Xamarin.Forms でアプリの設定情報を保存するには (Json.NET 使用) - Xamarin 日本語情報](http://ytabuchi.hatenablog.com/entry/2015/07/06/192526) の転載記事です。

前回は [Properties Dictionary を使用する方法](http://ytabuchi.hatenablog.com/entry/2015/07/06/181357) をご紹介しました。今回は Json.NET を使用する方法です。

## サンプル

[ｷﾞｯﾊﾌﾞ](https://github.com/ytabuchi/Study/tree/master/XF_DataSaveSample)

画面写真

<img src="http://cdn-ak.f.st-hatena.com/images/fotolife/y/ytabuchi/20150706/20150706191922.png" width="150" /> <img src="http://cdn-ak.f.st-hatena.com/images/fotolife/y/ytabuchi/20150706/20150706191927.png" width="150" />

<img src="http://cdn-ak.f.st-hatena.com/images/fotolife/y/ytabuchi/20150706/20150706191911.png" width="150" /> <img src="http://cdn-ak.f.st-hatena.com/images/fotolife/y/ytabuchi/20150706/20150706191919.png" width="150" />

<img src="http://cdn-ak.f.st-hatena.com/images/fotolife/y/ytabuchi/20150706/20150706191932.png" width="150" /> <img src="http://cdn-ak.f.st-hatena.com/images/fotolife/y/ytabuchi/20150706/20150706191934.png" width="150" />

> 2015/7/6 時点の最新版、Xamarin.Forms 1.4.3.6374 で確認しています。

まず、Xamarin でファイルを保存するには、[Xamarin.iOS／Androidでアプリの設定情報を保存するには？ - Build Insider](http://www.buildinsider.net/mobile/xamarintips/0030) の 3 にある、`System.IO.IsolatedStorage` を使うと簡単みたいです。

ただし、Xamarin.Forms の PCL では使用できないため、Dependency Service を使用する必要があります。Dependency Service については 
[Xamarin のドキュメント](http://developer.xamarin.com/guides/cross-platform/xamarin-forms/dependency-service/) をご参照ください。

> なお、[Xamarin の Working with Files ドキュメント](http://developer.xamarin.com/guides/cross-platform/xamarin-forms/working-with/files/#Loading_and_Saving_Files) では、Windows Phone でしか IsolatedStorage と Windows.Storage API がサポートされていないので、System.IO の API を Dependency Service を介して使うよ！とありますが、Build Insider にも書いてありますし、当たり前のように Android/iOS 共に IsolatedStorage 使えますね…

> 今回のサンプルは Xamarin のサンプルをちょろっと弄っただけですので、iOS/Android が `System.IO`、Windows Phone が `Windows.Storage` を使用しています。すみません。。

# やり方

必要に応じて Model を用意します。今回は ViewModel のみにします。

[Dependency Service 用の Interface](https://github.com/ytabuchi/Study/blob/master/XF_DataSaveSample/XF_DataSaveSample/XF_DataSaveSample/ISaveAndLoad.cs) を用意します。今回はメソッドを保存、復元、削除の 3つを用意しました。

```csharp
public interface ISaveAndLoad
{
    void SaveData(string filename, string text);
    string LoadData(string filename);
    bool ClearData(string filename);
}
```

## View ( [C#](https://github.com/ytabuchi/Study/blob/master/XF_DataSaveSample/XF_DataSaveSample/XF_DataSaveSample/Views/SaveToJsonCS.cs) , [Xaml](https://github.com/ytabuchi/Study/blob/master/XF_DataSaveSample/XF_DataSaveSample/XF_DataSaveSample/Views/SaveToJsonXaml.xaml)/[コードビハインド](https://github.com/ytabuchi/Study/blob/master/XF_DataSaveSample/XF_DataSaveSample/XF_DataSaveSample/Views/SaveToJsonXaml.xaml.cs) )

保存 ([vm インスタンスの値](https://github.com/ytabuchi/Study/blob/master/XF_DataSaveSample/XF_DataSaveSample/XF_DataSaveSample/ViewModels/AllPagesViewModel.cs) をそのまま `JsonConvert.SerializeObject` します。)：

```csharp
var json = JsonConvert.SerializeObject(vm);
DependencyService.Get<ISaveAndLoad>().SaveData("temp.json", json);
```


復元 (値を引っ張ってきて DeserializeObject し、BindingContext に再設定します)：  

```csharp
var data = DependencyService.Get<ISaveAndLoad>().LoadData("temp.json");
this.vm = JsonConvert.DeserializeObject<AllPagesViewModel>(data);
this.BindingContext = vm;
```



削除 (Dependency Service で bool 返すようにしました)：

```csharp
(DependencyService.Get<ISaveAndLoad>().ClearData("temp.json")) ? true : false; 
```

こんな感じですかね。


## OS 毎の実装

### [iOS](https://github.com/ytabuchi/Study/blob/master/XF_DataSaveSample/XF_DataSaveSample/XF_DataSaveSample.iOS/SaveAndLoad_iOS.cs), [Android](https://github.com/ytabuchi/Study/blob/master/XF_DataSaveSample/XF_DataSaveSample/XF_DataSaveSample.Droid/SaveAndLoad_Android.cs) 共に同じです。

```csharp
public void SaveData(string filename, string text)
{
    var documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
    var filePath = Path.Combine(documentsPath, filename);
    // File.WriteAllText ですべて上書きします。AppendAllText だと追加するようです。
    System.IO.File.WriteAllText(filePath, text);
}
public string LoadData(string filename)
{
    var documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
    var filePath = System.IO.Path.Combine(documentsPath, filename);
    // ファイルが無ければ null を返します。
    if (System.IO.File.Exists(filePath))
    {
        return System.IO.File.ReadAllText(filePath);
    }
    return null;
}
public bool ClearData(string filename)
{
    var documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
    var filePath = System.IO.Path.Combine(documentsPath, filename);
    System.IO.File.Delete(filePath);
    // ファイルが削除出来ていれば true, そうでなければ false を返します。
    return (System.IO.File.Exists(filePath)) ? false : true;
}
```

因みに以下のように IsolatedStorage を使用した場合は `System.Environment.SpecialFolder.Personal` とはファイルの PATH が違うみたいです。

```cs
using System.IO.IsolatedStorage;

var file = IsolatedStorageFile.GetUserStoreForApplication();
using (IsolatedStorageFileStream strm = file.CreateFile(filename))
{
    using (StreamWriter writer = new StreamWriter(strm))
    {
        writer.Write(text);
    }
}
```

### [Windows Phone](https://github.com/ytabuchi/Study/blob/master/XF_DataSaveSample/XF_DataSaveSample/XF_DataSaveSample.WinPhone/SaveAndLoad_WinPhone.cs)

```csharp
public string LoadData(string filename)
{
    var task = LoadDataAsync(filename);
    task.Wait(); // HACK: to keep Interface return types simple (sorry!)
    return task.Result;
}
async Task<string> LoadDataAsync(string filename)
{
    var local = Windows.Storage.ApplicationData.Current.LocalFolder;
    if (local != null)
    {
        var file = await local.GetItemAsync(filename);
        using (StreamReader streamReader = new StreamReader(file.Path))
        {
            var text = streamReader.ReadToEnd();
            return text;
        }
    }
    return "";
}
public async void SaveData(string filename, string text)
{
    var local = Windows.Storage.ApplicationData.Current.LocalFolder;
    var file = await local.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
    using (StreamWriter writer = new StreamWriter(await file.OpenStreamForWriteAsync()))
    {
        writer.Write(text);
    }
}

public bool ClearData(string filename)
{
    var task = ClearDataAsync(filename);
    task.Wait(); // HACK: to keep Interface return types simple (sorry!)
    return task.Result;
}

public async Task<bool> ClearDataAsync(string filename)
{
    var local = Windows.Storage.ApplicationData.Current.LocalFolder;
    await local.DeleteAsync();
    
    // ファイルが削除されているか？をGetFileAsyncがFileNotFoundExceptionを返すか？でチェックするのもダサいですよね。。
    try
    {
        var file = await local.GetFileAsync(filename);
    }
    catch (FileNotFoundException)
    {
        return true;
    }
    return false;
}
```


これで、無事 Json にデータを保存・復元できました。復元時に `BindingContext` に再設定するのがポイントだったようです。([@kurosawa0626](https://twitter.com/kurosawa0626)さん、[@ticktackmobile](https://twitter.com/ticktackmobile) さん、ありがとうございました！) 何かの参考になれば幸いです。

# Xamarin 気になった方は

是非 [ダウンロード(直接)](http://xamarin.com/download) / [ダウンロード(弊社経由)](https://www.xlsoft.com/jp/products/download/index.html?option=Xamarin&r=ythb) して触ってみてください。
[学習用リソース](http://ytabuchi.hatenablog.com/entry/2014/08/26/191759) や [JXUG リンクページ](http://jxug.org/links.html) に参考資料を纏めてますので併せてどうぞ。

<a href="http://blog.hatena.ne.jp/ytabuchi/ytabuchi.hatenablog.com/subscribe">Xamarin の情報が欲しい方はこのブログも購読いただいたりすると嬉しいです。</a>

以上です。