Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 
Xamarin逆引きTips

Xamarin逆引きTips

Xamarin.Formsでローカルデータベースを使用するには?

2015年5月20日

アプリを終了して再起動したときに、ユーザーデータを復活させたい場合、ローカルやクラウドにデータを保存することになる。その一つの方法として、SQLite.Netを使ってローカルDBに保存する方法を説明する。

古谷 誠進(@furuya02
  • このエントリーをはてなブックマークに追加

1. ローカルデータベース

 ユーザーが扱っているデータを、アプリを終了しても次回起動時に、再度扱いたい場合がある。このような場合、ローカルのファイルシステムやクラウド上にデータを保存する仕組みが必要になる。

 今回は、そのための手法の一つとして、簡単にローカルにデータベースを構築できるSQLite.Netの使用方法を紹介する*1

  • *1 なお本Tipsは、Windows上でVisual Studio 2013を使用してXamarin.Forms開発をすることを前提としている(編集部注: Mac上のXamarin Studioでも同様の手順で、本稿の内容が実現できることは確認している)。使用しているXamarin.Formsのバージョンは、プロジェクト作成時に利用されている「1.3.1.6296」である。

2. Xamarin.Formsプロジェクトを作成する

 メニューバーの[ファイル]-[新規作成]-[プロジェクト]から表示したダイアログで、[テンプレート]-[Visual C#]-[Mobile Apps]-[Blank App (Xamarin.Forms Portable)]を選択し、名前を「SQLiteSample」として[OK]ボタンを押す。

図1 「Blank App (Xamarin.Forms Portable)」の新規作成

3. SQLite.Netパッケージの追加

 SQLite.NetはNuGetパッケージで簡単にインストールできる。

 Visual StudioでのNuGetパッケージのインストールは、メニューバーの[ツール]-[NuGet パッケージマネージャー]-[ソリューションの NuGet パッケージの管理]を選択して表示された[NuGet パッケージの管理]ダイアログから行う。検索欄に「SQLite.Net.PCL」と入力すると、目的のパッケージを簡単に見つけることができる。

図2 パッケージマネージャー

 図2の[インストール]ボタンを押した後、続いて表示される[プロジェクトの選択]ダイアログでは、全てのプロジェクトにチェックを入れて[OK]ボタンを押す(図3)*2

図3 全てのプロジェクトを選択する
図3 全てのプロジェクトを選択する
  • *2 WindowsPhoneの場合は、依存関係からSQLite.Net-PCLの他にsqlite-net-wp8 3.8.5もインストールされる。

4. インターフェースの定義

 SQLite.Netで使用されるデータベースの実体は、ローカルに保存されるファイルである。ローカルのファイル操作やパス指定については、プラットフォーム固有の処理となるため、SQLite.Netでも、その初期化処理で、プラットフォームごとに用意されたメソッドを使用する。

 今回は、このSQLite.Netの初期化にあたる、SQLiteConnectionインスタンスを取得するまでの処理を、DependencyService*3を使用して、PCL(Portable Class Library)から共通的に使用できるようにした。

 DependencyServiceで使用するインターフェースの定義のために、PCLプロジェクトであるSQLiteSampleプロジェクトにISQLite.csファイルを追加して、以下のように修正した。

C#
using SQLite.Net;
namespace SQLiteSample {
  public interface ISQLite {
    SQLiteConnection GetConnection(); // <-1
  }
}
DependencyServiceのためのインターフェースを定義するコード(ISQLite.cs)

 GetConnectionメソッドを実行すると、SQLiteConnectionのインスタンスが取得できる(1)。

5. 各プラットフォームの実装

 続いて、プラットフォームごとの処理を実装していく。

(1)iOS

 iOS用のプロジェクトであるSQLiteSample.iOSプロジェクトにSQLite_iOS.csファイルを追加し、以下のように修正する。

C#
using System;
using System.IO;
using SQLite.Net;
using SQLite.Net.Platform.XamarinIOS;
using SQLiteSample.iOS;
using Xamarin.Forms;

[assembly: Dependency(typeof(SQLite_iOS))] // <-1
namespace SQLiteSample.iOS {
  public class SQLite_iOS : ISQLite {
    public SQLiteConnection GetConnection() {
      var documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal); // <-2
      var libraryPath = Path.Combine(documentsPath, "..", "Library"); // <-3
      var path = Path.Combine(libraryPath, "TodoSQLite.db3");         // <-4
      return new SQLiteConnection(new SQLitePlatformIOS(), path);     // <-5
    }
  }
}
DependencyServiceのためのプラットフォーム側のコード(SQLite_iOS.cs)

 1は、DependencyServiceでプラットフォーム側の固有の処理を記述する場合の定型句である。

 Environment.GetFolderPathメソッド*4を使うと、特定のフォルダーのパスを取得できる。ここでは、パラメーターにEnvironment.SpecialFolder列挙体のPersonalを指定しているので、ユーザーディレクトリのDocumentsフォルダーが取得されている(2)。

 続いて、このDocumentsフォルダーを基準として、その親フォルダーに位置するLibraryというフォルダーを取得している(3)。ここで使用されているPathクラスの静的メソッドであるCombineは、2つの文字列要素を結合して、パスを作成するためのものである。

 最後にここまでで取得したフォルダーと、データベース名(ここではTodoSQLite.db3とした)を組み合わせて、最終的なデータベースファイル名としている(4)。

 SQLite.Netでは、SQLiteConnectionオブジェクトのインスタンスを使用して、各種の操作を行うことになるが、このインスタンスの生成には、データベースのファイル名の他に、プラットフォーム固有のオブジェクトが必要になる。そして、SQLitePlatformIOSオブジェクトが、iOSでのそれになる(5)。

(2)Android

 Android用のプロジェクトであるSQLiteSample.Droidプロジェクトには、SQLite_Droid.csファイルを追加し、以下のように修正する。

C#
using System;
using System.IO;
using SQLite.Net;
using SQLite.Net.Platform.XamarinAndroid;
using SQLiteSample.Droid;
using Xamarin.Forms;

[assembly: Dependency(typeof(SQLite_Android))] 
namespace SQLiteSample.Droid {
   public class SQLite_Android : ISQLite {
    public SQLiteConnection GetConnection() {
      var documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal); // <-1
      var path = Path.Combine(documentsPath, "TodoSQLite.db3"); // <-2
      return new SQLiteConnection(new SQLitePlatformAndroid(), path); // <-3
    }
  }
}
DependencyServiceのためのプラットフォーム側のコード(SQLite_Droid.cs)

 Androidでは、Environment.SpecialFolder.Personalを指定してEnvironment.GetFolderPathメソッドを呼び出すと、アプリケーションごとのデータフォルダーのパスが取得できる(1)。

 ここでは、このデータフォルダーの中にデータベースファイルを置くことにした(2)。

 SQLiteConnectionクラスのインスタンス生成で必要になる、プラットフォーム固有のオブジェクトは、AndroidではSQLitePlatformAndroidオブジェクトである(3)。

6. データの定義

 サンプル用のデータとして、PCLプロジェクトであるSQLiteSampleプロジェクトにTodoItem.csファイルを追加して、以下のように修正する。

C#
using System;
using SQLite.Net.Attributes;

namespace SQLiteSample {
  public class TodoItem {
    [PrimaryKey, AutoIncrement]
    public int Id { get; set; }             // <-1
    public string Text { get; set; }        // <-2
    public DateTime CreatedAt { get; set; } // <-3
    public bool Delete { get; set; }        // <-4
  }
}
データクラスのコード(TodoItem.cs)

 キーとなるId1)の他に、文字列を保存するText2)、作成日時のCreatedAt4)、そして、削除状態を表現するDelete5)がある。

 サンプルアプリでは、Deletetrueのとき、削除されたレコードということで、表示の対象外としている(そのコードは後述する)。

7. データベースへのアクセスクラス

 データベースへアクセスするクラスとして、PCLプロジェクトであるSQLiteSampleプロジェクトにTodoRepository.csを追加して、以下のように修正した。

C#
using System.Collections.Generic;
using SQLite.Net;
using Xamarin.Forms;

namespace SQLiteSample {
  class TodoRepository {

    static readonly object Locker = new object(); // <-1
    readonly SQLiteConnection _db; // <-2

    public TodoRepository() {
      _db = DependencyService.Get<ISQLite>().GetConnection(); // <-3
      _db.CreateTable<TodoItem>(); // <-4
    }

    public IEnumerable<TodoItem> GetItems() { // <-5
      lock (Locker) { // <-6
        // Delete==falseの一覧を取得する(削除フラグが立っているものは対象外)
        return _db.Table<TodoItem>().Where(m => m.Delete == false); // <-7
      }
    }

    public int SaveItem(TodoItem item) { // <-8
      lock (Locker) { // <-9
        if (item.Id != 0) { // Idが0でない場合は、更新
          _db.Update(item); // <-10
          return item.Id;
        }
        return _db.Insert(item); // <-11
      }
    }
  }
}
データベースにアクセスするクラスのコード(TodoRepository.cs)

 2は、SQLite.Netでデータを操作する際に使用する、SQLiteConnectionクラスのインスタンスを保持する変数である。

 この変数は、コンストラクターで初期化されているが、ここでは、DependencyServiceで定義した、GetConnectionメソッドを使用している(3)。

 そして4は、テーブルを初期化する処理であり、指定したデータ型(ここではTodoItem)とデータベース上のテーブルが一致していない場合は、この時点で生成される。

 TodoRepositoryクラスでは、データベースを操作する2つのメソッドを定義した。

 その1つ目は、一覧のためのGetItemsメソッドである(5)。Deleteプロパティがfalseのデータだけが表示対象であるため、それをLINQのWhereメソッドで抽出している(7)。

 2つ目は、追加および更新のSaveItemメソッドである(8)。パラメーターで受け取ったTodoItemオブジェクトのIdプロパティが、0でない場合は、更新とし(10)、0の場合は、新規の追加となっている(11)。

 最後にロックの機能であるが、このデータベース・アクセス・クラスをスレッドセーフとするために、データベースにアクセスする前後でロック処理をしている(69)。そして、(1)は、そのロックオブジェクトである。

8. データベースの操作

 ここまでの作業で、SQLite.Netを使用するための準備は整った。最後に、画面を作成して、データベースを操作するためのコードとして、SQLiteSampleプロジェクトのApp.csファイルを以下のように修正する。

C#
using System;
using Xamarin.Forms;

namespace SQLiteSample {
  public class App : Application {
    public App() {
      MainPage = new MyPage();
    }
  }

  class MyPage : ContentPage {

    readonly TodoRepository _db = new TodoRepository(); // <-1

    public MyPage() {

      var listView = new ListView { // <-2
        ItemsSource = _db.GetItems(), // <-3
        ItemTemplate = new DataTemplate(typeof(TextCell)) // <-4
      };
      listView.ItemTemplate.SetBinding(TextCell.TextProperty, "Text");
      listView.ItemTemplate.SetBinding(TextCell.DetailProperty, new Binding("CreatedAt", stringFormat: "{0:yyy/MM/dd hh:mm}"));
      listView.ItemTapped += async (s, a) => { // <-5
        var item = (TodoItem)a.Item;
        if (await DisplayAlert("削除してい宜しいですか", item.Text, "OK", "キャンセル")) {
          item.Delete = true; // 削除フラグを有効にして
          _db.SaveItem(item); // データベースの更新
          listView.ItemsSource = _db.GetItems(); // リスト更新
        }
      };
      var entry = new Entry { // <-6
        HorizontalOptions = LayoutOptions.FillAndExpand
      };
      var buttonAdd = new Button { // <-7
        WidthRequest = 60,
        TextColor = Color.White,
        Text = "Add"
      };
      buttonAdd.Clicked += (s, a) => { // <-8
        if (!String.IsNullOrEmpty(entry.Text)) { // Entryに文字列が入力されている場合に処理する
          var item = new TodoItem { Text = entry.Text, CreatedAt = DateTime.Now, Delete = false };
          _db.SaveItem(item);
          listView.ItemsSource = _db.GetItems(); //リスト更新
          entry.Text = ""; // 入力コントロールをクリアする
        }
      };

      Content = new StackLayout { // <-9
        Padding = new Thickness(0, Device.OnPlatform(20, 0, 0), 0, 0),
        Children = {
          new StackLayout {
            BackgroundColor = Color.Navy, // 入力部の背景色はネイビー
            Padding = 5,
            Orientation = StackOrientation.Horizontal,
            Children = {entry, buttonAdd} // Entryコントロールとボタンコントロールを横に並べる
          },
          listView // その下にリストボックスを置く
        }
      };
    }

  }
}
データベースを操作するコード(App.cs)

 1は、先に定義したデータベースへのアクセス・クラスのTodoRepositoryである。以降、このオブジェクトを使用して、データの追加や更新を行うことになる。

 画面に配置しているコントロールは、Entryコントロール(6)、Buttonコントロール(7)、およびListViewコントロール(2)の3つであり、StackLayoutを組み合わせて配置している(9)。

 ListViewコントロールでは、そのデータソースをTodoRepositoryの一覧メソッドであるGetItemsメソッドで初期化している(3)。また、データを表示するためのデータテンプレートも指定している(4)。

 Buttonコントロールをクリックしたときのイベントハンドラー内で、Entryコントロールのテキストを使って新規にTodoItemクラスのオブジェクトを生成し、TodoRepositorySaveItemメソッドで追加している(8)。Idプロパティが0であるため、追加となるのである。

 ListViewコントロールのタップイベントでは、選択された行のDeleteプロパティをTrueに変更して、TodoRepositorySaveItemメソッドで更新している(5)。今度は、Idプロパティが0でないため、更新処理となっている。また、更新後にデータソースをGetItemsメソッドで最新の状態にしている。

 このコードを実行すると次のような画面になる。

図4 データベースを操作している画面(iOS) 図4 データベースを操作している画面(Android)
図4 データベースを操作している画面(iOS/Android)

9. まとめ

 今回紹介したように、SQLite.Netを使用すると、簡単にローカルにデータベースを構築できる。

 なお、クラウド上のデータベースでデータを保持する場合も、このようなローカルのデータベースと組み合わせることで、オフラインのときでも継続して作業ができるアプリが構築可能である。

※以下では、本稿の前後を合わせて5回分(第48回~第52回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

Xamarin逆引きTips
48. Xamarin.Formsでプラットフォームごとの微調整を行うには?

カスタムレンダラーやDependencyServiceの仕組みを使わず、Deviceクラスを利用してプラットフォーム間で異なる部分を微調整する方法を説明する。

Xamarin逆引きTips
49. MvvmCrossでAndroidの画面の再生成に対応するには?

Androidアプリでは別アプリ移動時に画面が破棄され、アプリ再表示時に画面が復元される場合がある。この画面の再生成を、MvxViewModelのライフサイクルメソッドにより行う方法を説明する。

Xamarin逆引きTips
50. 【現在、表示中】≫ Xamarin.Formsでローカルデータベースを使用するには?

アプリを終了して再起動したときに、ユーザーデータを復活させたい場合、ローカルやクラウドにデータを保存することになる。その一つの方法として、SQLite.Netを使ってローカルDBに保存する方法を説明する。

Xamarin逆引きTips
51. MvvmCrossでカスタムコンバーターを作成するには?

MvvmCrossでのiOS/Androidアプリ開発において、バインディングする値を変換できるカスタムコンバーターの使い方を説明する。

Xamarin逆引きTips
52. Xamarin.FormsでTwitterクライアントを作成するには?

TwitterのAPIを扱えるライブラリであるCoreTweetを使用して、Twitterデータを検索するアプリを作成。CoreTweetの導入と、検索したテキストの表示までを紹介する。

サイトからのお知らせ

Twitterでつぶやこう!