C#
Xamarin
Xamarin.Forms
Realm
RealmXamarin

Xamarin.Forms と Realm を使用した CRUD アプリチュートリアル

More than 1 year has passed since last update.

Almir Vuk 氏による Xamarin.Forms and Realm: CRUD Example の日本語訳です。

Realm 本家のサイトでも CRUD 操作を個別に解説していますが、1つのアプリにするチュートリアルのようなものは珍しいと思ったので、一応 Almir Vuk 氏に連絡をとった上で紹介することにしました。

スクリーンショット 2017-08-12 13.02.30.png

あくまでローカルの SQLite を置き換えるユースケースです。 Azure で Realm Object Server を使う、リアルタイム同期、認証、サーバープッシュするみたいなケースではないのでその点は注意が必要です。


このブログ記事では、 Realm をモバイルデータベースとして使用して簡単なアプリケーションを作成し、 Xamarin.Forms の Realm で CRUD 操作を行う方法を示します。このプロジェクトのコードは私のGitHubページで利用できます。

https://github.com/almirvuk/Xamarin.Forms_Realm_Example

Realm に慣れていない方のために、公式の Web サイトからいくつかの文章を紹介します。 注意: 次のブログ記事で長所・短所について書きます。

Realm モバイルデータベースはモバイルデバイス上で実行するように構築されています。従来のデータベースとは異なり、 Realm のオブジェクトはネイティヴオブジェクトです。オブジェクトをデータベースからコピーして変更し、保存する必要はありません。つまり、いつも本物のオブジェクトそのものを扱うことができます。あるスレッドまたはプロセスがオブジェクトを変更すると、他のスレッドやプロセスに即座に通知することができます。

Realm モバイルデータベースはオープンソース( Apache ライセンスを使用)でクロスプラットフォームであり、 Android、iOS、Xamarin(.NET)、React Native で利用可能なライブラリがあります。 Realm はプラットフォーム間で完全に互換性があります。

Realm モバイルデータベースはすべて単独で使用できます。

The Realm Mobile Platform より

私が SQLite の代わりを探していて、初めて Realm を見つけたとき衝撃的でした。

始めるのはとても簡単で、 Nuget パッケージをいくつか揃えればモバイルアプリでモデルとデータアクセス層をコーディングする準備は OK です。

このチュートリアルでは、 Realm をモバイルデータベースとして使用してアプリケーションで CRUD を達成する方法を説明します。

それでは始めましょう。

セットアップ

新しく Xamarin.Forms アプリを作成してください。私の例では Sharedコードプロジェクト用に PCL を使います。

普段通り標準的なフォルダを追加します。 Models、 Views、 ViewModelsです。アーキテクチャパターンとしてMVVMを利用します。


訳注:

最近のテンプレートでは元々 Models、 Views、 ViewModels フォルダが準備されてますね。

手順を同じように追う場合は

  • Blank Forms App、Use Portable Class Library
  • Use XAML for interface files

で作るとよさげです。

プロジェクト名は Xamarin.Forms_Realm にすると、サンプルコードをコピペしたくなったときに便利です。

ふんだんにアイコンを使用しているので

  • 自分で準備する
  • リポジトリの各プラットフォームのリソースから拝借する
  • 別の要素に置き換える

とかしましょう。


スクリーンショット 2017-08-11 18.13.06.png
(オリジナル記事より)

Realm データベースを使用するには、 nuget パッケージを追加する必要があります。ソリューションの「 NuGet パッケージの追加...」に移動し、このように Realm の nuget を探します。

スクリーンショット 2017-08-11 18.23.15.png
(オリジナル記事より)

PCL とその他( Android、UWP、iOS )の全てのプロジェクトに忘れずにインストールします。少し待つとこのメインの nuget が依存する他の nuget もインストールし終わります。

準備ができました。

このアプリはチームと選手に関するものなので、 Models フォルダーに Team とPlayers を表す2つのクラスを作成します。

チームと選手には1:Nの関係があるので、 Player クラスでは Team 型のプロパティを「外部」プロパティとして持ち、 Team クラスでは Player 型の List を持ちます。このように設定すると、2つのクラス間に1対多の関係ができます。

Realm をモバイルデータベースとして使用するには、データベースで「 create table 」する必要があります。そうするためにはテーブルになるクラスが RealmObject を継承する必要があります。

2つのクラスは下記の通りです。

Team.cs
using Realms;
using System;
using System.Collections.Generic;

namespace Xamarin.Forms_Realm.Models {

    public class Team : RealmObject {

        [PrimaryKey]
        public string TeamId { get; set; } = Guid.NewGuid().ToString();

        public string Title { get; set; }

        public string Manager { get; set; }
        public string City { get; set; }

        public string StadiumName { get; set; }

        public IList<Player> Players { get;  }
    }
}

(訳注: 記事に Player.cs なかったですが、リポジトリ見るとこうでした)

Player.cs
using Realms;
using System;

namespace Xamarin.Forms_Realm.Models {

    public class Player : RealmObject{

        [PrimaryKey]
        public string PlayerId { get; set; } = Guid.NewGuid().ToString();

        public string Name { get; set; }
        public int JerseyNumber { get; set; }
        public string Position { get; set; }

        public Team Team { get; set; }
    }
}

ご覧の通り、プライマリーキーを宣言するために、 string 型のプロパティに [PrimaryKey] というデータアノテーションを使用していて、それは自動生成される GUID です。 Realmではまだプライマリーキーのオートインクリメントはサポートされていない 点が重要です。

関係(リレーション)を持ったモデルの準備が整ったので、データの問い合わせを開始する必要があります。

READ

ここで、 TeamsListPage.xaml というビューフォルダ内に View / Page を1つ作成します。これは次のようになります。

TeamsListPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Xamarin.Forms_Realm.Views.TeamsListPage"
             Title="All Teams">

    <ContentPage.ToolbarItems>
        <ToolbarItem Text="Add new"
                     Icon="ic_add.png"
                     Command="{Binding AddTeamCommand}" />
    </ContentPage.ToolbarItems>

    <ContentPage.Content>

        <ListView ItemsSource="{Binding AllTeams}"
                  HasUnevenRows="True"
                  ItemTapped="TeamTapped"
                  BackgroundColor="#f5f5f5">

            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>

                        <Grid BackgroundColor="White"
                              Margin="4">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>


                            <Label Grid.Row="0"
                                   Text="{Binding Title}"
                                   FontSize="Medium"
                                   Margin="4"
                                   FontAttributes="Bold" />

                            <StackLayout Orientation="Horizontal"
                                         Grid.Row="1"
                                         Margin="4"
                                         Padding="2">

                                <Label Text="{Binding StadiumName}"
                                       FontSize="Small" />

                                <Label Text="{Binding City}"
                                       FontSize="Small" />
                            </StackLayout>
                        </Grid>

                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>


    </ContentPage.Content>
</ContentPage>

この xaml ページはほとんど説明することがありません。

最上部には、新しいページを開き、データベースに新しいチームを追加するためのツールバーが1つあります。

view の主要部分は ListView コントロールであり、この ListView のアイテムソースはこのステップの後で作成する view model における List へのバインディングです。

ItemTemplate では ListView アイテムの構造(List View アイテムの見た目)を作成します。グリッド内のラベルは、 Team 型のリストアイテムのプロパティにバインドされています。なぜならこの ListView はデータベースの Team を表示するためです。

view model を作成する前に、 ViewModels フォルダに BaseViewModel クラスを追加します。このクラスはOnPropertyChangedメソッドを持ちます。すべての view model はそれを継承し、プロパティに対して OnPropertyChanged メソッドを呼び出すことができます。

BaseViewModel は次の通りです。

BaseViewModel.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace Xamarin.Forms_Realm.ViewModels {

    public class BaseViewModel : INotifyPropertyChanged {

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string propertyName = "") =>
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

もう view があるので、 view model を作ることができます。 ViewModels フォルダに TeamListViewModel.cs を追加してください。

TeamListViewModel.cs
using Realms;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;
using Xamarin.Forms_Realm.Models;
using Xamarin.Forms_Realm.Views;

namespace Xamarin.Forms_Realm.ViewModels {

    public class TeamListViewModel : BaseViewModel{

        private ObservableCollection<Team> allTeams;
        public ObservableCollection<Team> AllTeams {
            get { return allTeams; }
            set { allTeams = value;
            }
        }

        public ICommand AddTeamCommand { get; private set; }

        public TeamListViewModel() {

            Realm context = Realm.GetInstance();

            AllTeams = new ObservableCollection<Team>(context.All<Team>());

            AddTeamCommand = new Command(async ()=> await Application.Current.MainPage.Navigation.PushAsync(new AddTeamPage()));
        }
    }
}

28行目

(訳注: Realm context = Realm.GetInstance();

Realm 型の新しいオブジェクトを作成しています。このオブジェクトは基本的にはモバイルデータベースを表現しています。

それを使うことで CRUD 操作することができます。

30行目

(訳注: AllTeams = new ObservableCollection<Team>(context.All<Team>());

context.All() の結果から AllTeams という ObservableCollection を初期化しています。

ALL メソッドの構文はとてもシンプルです。

context.All<TYPE_OF_CLASS_TO_GET>():

IQueryable を返すので、それに対して LINQ を使用できます。

ご覧の通りデータの問い合わせは、同じではありませんが EntityFramework と似ています。このコースの後半ではリストに対して Where やその他の条件で問い合わせます。

コンストラクターの最後で AddTeamCommand を初期化しています。ユーザがツールバーのアイテムをクリックすると次のステップで作成する AddTeamPage に遷移します。

また、 view のコードビハインドから ViewModel に対するバインディングコンテキストを設定することを忘れないでください。

TeamsListPage.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using Xamarin.Forms_Realm.Models;
using Xamarin.Forms_Realm.ViewModels;

namespace Xamarin.Forms_Realm.Views {

    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class TeamsListPage : ContentPage {

        public TeamsListPage() {
            InitializeComponent();
        }

        protected override void OnAppearing() {
            BindingContext = new TeamListViewModel();
        }

        private async void TeamTapped(object sender, ItemTappedEventArgs e) {
           // TODO
        }
    }
}

空のリストビューしか取得できないですし意味がないので、アプリを実行しません。しかし、Realm部分が例外を投げないか確認する場合はリビルド・実行してください。コードの動作確認もできます。

チームをリスト表示するページができました。次は新しいチームを追加する( create ) view を作りましょう。

CREATE

Views フォルダで AddTeamPage.xaml という新しいページを作成します。これは次のようになります。

AddTeamPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Xamarin.Forms_Realm.Views.AddTeamPage"
             Title="Add new team">

    <ContentPage.ToolbarItems>
        <ToolbarItem Text="Save"
                     Icon="ic_save.png"
                     Command="{Binding SaveTeamCommand}" />
    </ContentPage.ToolbarItems>

    <ContentPage.Content>

        <StackLayout>

            <Entry Placeholder="Team Name/Title"
                   Text="{Binding Title}"
                   Margin="4" />

            <Entry Placeholder="Manager name"
                   Text="{Binding Manager}"
                   Margin="4" />

            <Entry Placeholder="City name"
                   Text="{Binding City}"
                   Margin="4" />

            <Entry Placeholder="Name of the stadium"
                   Text="{Binding StadiumName}"
                   Margin="4" />

        </StackLayout>

    </ContentPage.Content>
</ContentPage>

ご覧の通り、 Team プロパティのデータ入力する Entry コントロールとデータベースでの save/create 操作をハンドルするツールバーのあるとてもシンプルな「入力フォーム」ページです。

次のステップはいつものようにビューモデルを追加することです。規約に従えば
AddTeamViewModel.cs になります。

AddTeamViewModel.cs
using Realms;
using System.Windows.Input;
using Xamarin.Forms;
using Xamarin.Forms_Realm.Models;

namespace Xamarin.Forms_Realm.ViewModels {

    public class AddTeamViewModel : BaseViewModel{

        private string title;
        public string Title {
            get { return title; }
            set { title = value;
                OnPropertyChanged("Title");
            }
        }

        private string manager;
        public string Manager {
            get { return manager; }
            set { manager = value;
                OnPropertyChanged("Manager");
            }
        }

        private string city;
        public string City {
            get { return city; }
            set { city = value;
                OnPropertyChanged("City");
            }
        }

        private string stadiumName;
        public string StadiumName {
            get { return stadiumName; }
            set { stadiumName = value;
                OnPropertyChanged("StadiumName");
            }
        }

        public ICommand SaveTeamCommand { get; private set; }

        public AddTeamViewModel() {

            SaveTeamCommand = new Command(SaveTeam);
        }

        async void SaveTeam() {

            Realm context = Realm.GetInstance();

            context.Write(() =>  {

                context.Add<Team>(new Team() { City = City, StadiumName = StadiumName, Title = Title, Manager = Manager });
            });

            /* 
            * Also you can use it like this
            *   using (var transaction = context.BeginWrite()) {
            *       context.Add<Team>(new Team() { City = City, StadiumName = StadiumName, Title = Title, Manager = Manager });
            *      transaction.Commit();
            *   }
            */

            // After adding new entry to database close this page
            await Application.Current.MainPage.Navigation.PopAsync();
        }

    }
}

view model はとてもシンプルです。いくつかプロパティがありますが、セッター内に OnPropertyChangedOnPropertyChanged があります。標準的な
MVVM アプローチではプロパティが変更されるとそれにバインドされる XAML のコントロールは更新された値を持ちます。

私のブログには MVVM 記事がいくつかあるので、馴染みがなければチェックしてみてください。

また、 view model の主要な部分には SaveTeam() メソッドを呼び出す Command があります。

SaveTeam の中でまた Realm オブジェクトを呼び出し、 context と名付けています。その下では DB で挿入を行う2つの方法があります。

context.Write() を使い、新しい delegate の中にはこのような挿入を行う主要部分があります。

context.Add<TYPE_OF_OBJECT_TO_INSERT>(OBJECT_TO_INSERT);

また、BeginWriteメソッドから取得する新しいトランザクションを作成する必要がある場合に使用する方法があります。変更を適用するには、トランザクションをコミットすることが重要です。

データベースに新しいエントリーを追加した後、このページを閉じます。

viewのコードビハインドにバインディングコンテキストを追加することを忘れないでください。

using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using Xamarin.Forms_Realm.ViewModels;

namespace Xamarin.Forms_Realm.Views {

    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class AddTeamPage : ContentPage {

        public AddTeamPage() {
            InitializeComponent();
        }

        protected override void OnAppearing() {

            BindingContext = new AddTeamViewModel();
            base.OnAppearing();
        }
    }
}

もうアプリを実行してテストできます。

スクリーンショット 2017-08-11 22.28.38.png
(オリジナル記事より)

動きました!

チュートリアルを続けましょう。

DELETE

次のステップでは、チームの詳細を表示し、チームにプレーヤーを追加し、必要に応じてチームを削除します。

最初のステップは、Views フォルダに TeamDetailsPage.xaml を作成することです。

TeamDetailsPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Xamarin.Forms_Realm.Views.TeamDetailsPage"
             Title="About team">

    <ContentPage.ToolbarItems>
        <ToolbarItem Text="Add player"
                     Command="{Binding AddPlayerCommand}"
                     Icon="ic_add_player.png" />

        <ToolbarItem Text="Edit team"
                     Icon="ic_edit.png"
                     Command="{Binding EditTeamCommand}" />

        <ToolbarItem Text="Delete team"
                     Icon="ic_delete.png"
                     Command="{Binding DeleteTeamCommand}" />
    </ContentPage.ToolbarItems>

    <ContentPage.Content>
        <ScrollView>

            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>

                <Label Text="{Binding Title}"
                       Margin="8"
                       FontSize="Medium"
                       Grid.Row="0" />

                <Label Text="{Binding Manager}"
                       Grid.Row="1"
                       FontSize="Small"
                       Margin="8" />

                <Label Text="{Binding City}"
                       Grid.Row="2"
                       FontSize="Small"
                       Margin="8" />

                <Label Text="{Binding StadiumName}"
                       Grid.Row="3"
                       FontSize="Small"
                       Margin="8" />

                <StackLayout Grid.Row="4"
                             Margin="8">

                    <Label Text="Players:"
                           FontSize="Small"
                           Margin="4" />

                    <ListView ItemsSource="{Binding Players}"
                              HasUnevenRows="True"
                              ItemTapped="OnPlayerTapped"
                              Margin="4">

                        <ListView.ItemTemplate>
                            <DataTemplate>
                                <ViewCell>

                                    <StackLayout Orientation="Horizontal"
                                                 HorizontalOptions="StartAndExpand">

                                        <Label Text="{Binding Name}"
                                               FontSize="Small"
                                               Margin="4"/>

                                        <Label Text="{Binding Position}"
                                               FontSize="Small"
                                               Margin="4" />

                                        <Label Text="{Binding JerseyNumber}"
                                               FontSize="Small"
                                               Margin="4"/>

                                        <Image Source="ic_edit.png"
                                               HeightRequest="17"
                                               HorizontalOptions="End"
                                               VerticalOptions="Center"
                                               Margin="4" />
                                    </StackLayout>

                                </ViewCell>
                            </DataTemplate>
                        </ListView.ItemTemplate>
                    </ListView>

                </StackLayout>

            </Grid>
        </ScrollView>

    </ContentPage.Content>
</ContentPage>

上部には、次のステップで実装するアクションのためのツールバーアイコンが2つあります。

そして Team のためのデータを保持するラベルがあり、その下には、そのチームの選手のためのシンプルなListViewがあります。

アクションは、新しいプレイヤーを追加し、このチームを編集して削除します。

ここでは、この詳細ビューの view model から始めましょう。

TeamDetailsViewModel.cs
using Realms;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
using Xamarin.Forms;
using Xamarin.Forms_Realm.Models;
using Xamarin.Forms_Realm.Views;

namespace Xamarin.Forms_Realm.ViewModels {

    public class TeamDetailsViewModel : BaseViewModel {

        private string title;
        public string Title {
            get { return title; }
            set {
                title = value;
                OnPropertyChanged("Title");
            }
        }

        private string manager;
        public string Manager {
            get { return manager; }
            set {
                manager = value;
                OnPropertyChanged("Manager");
            }
        }

        private string city;
        public string City {
            get { return city; }
            set {
                city = value;
                OnPropertyChanged("City");
            }
        }

        private string stadiumName;
        public string StadiumName {
            get { return stadiumName; }
            set {
                stadiumName = value;
                OnPropertyChanged("StadiumName");
            }
        }

        private ObservableCollection<Player> players;
        public ObservableCollection<Player> Players {
            get { return players; }
            set { players = value; }
        }

        public ICommand AddPlayerCommand { get; private set; }
        public ICommand EditTeamCommand { get; private set; }
        public ICommand DeleteTeamCommand { get; private set; }

        private string _teamId;

        public TeamDetailsViewModel(string teamId) {

            _teamId = teamId;

            Realm context = Realm.GetInstance();

            var team = context.Find<Team>(teamId);

            // Setting property values from team object
            // that we get from database
            Title = team.Title;
            City = team.City;
            StadiumName = team.StadiumName;
            Manager = team.Manager;

            // You can not do like this:
            // Players = new ObservableCollection<Player>(context.All<Player>().Where(p => p.Team.TeamId == teamId));
            // Querying by nested RealmObjects attributes is not currently supported:
            Players = new ObservableCollection<Player>(context.All<Player>().Where(p => p.Team == team));

            // Commands for toolbar items
            AddPlayerCommand = new Command(async () => await Application.Current.MainPage.Navigation.PushAsync(new AddPlayerPage(team.TeamId)));
            EditTeamCommand = new Command(async () => await Application.Current.MainPage.Navigation.PushAsync(new EditTeamPage(team.TeamId)));
            DeleteTeamCommand = new Command(DeleteTeam);
        }

        void DeleteTeam() {

            Realm context = Realm.GetInstance();
            var team = context.Find<Team>(_teamId);

            context.Write(() => {
                context.Remove(team);
            });

            Application.Current.MainPage.Navigation.PopAsync();
        }
    }
}

とてもシンプルな view model で、チーム詳細のプロパティがいくつかと、チーム選手の ObservableCollection が1つと、コマンドがいくつかあります。

view model のコンストラクターでは TeamsListPage.xaml.cs の TeamTapped イベントから渡される teamID を渡しています。実装を見てみましょう。

TeamsListPage.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using Xamarin.Forms_Realm.Models;
using Xamarin.Forms_Realm.ViewModels;

namespace Xamarin.Forms_Realm.Views {

    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class TeamsListPage : ContentPage {

        public TeamsListPage() {
            InitializeComponent();
        }

        protected override void OnAppearing() {
            BindingContext = new TeamListViewModel();
        }

        private async void TeamTapped(object sender, ItemTappedEventArgs e) {

            if (e.Item != null) {
                var team = (Team)e.Item;
                await Navigation.PushAsync(new TeamDetailsPage(team.TeamId));
            }
        }
    }
}

これは、 ListView のタップアイテムから値を取得する一般的な方法であり、
TeamDetailsPage に移動して teamId の値を渡します。

TeamDetailsPage.xaml.cs は次のようになります。

TeamDetailsPage.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using Xamarin.Forms_Realm.Models;
using Xamarin.Forms_Realm.ViewModels;

namespace Xamarin.Forms_Realm.Views {

    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class TeamDetailsPage : ContentPage {

        private string _teamId;

        public TeamDetailsPage(string teamId) {
            InitializeComponent();
            _teamId = teamId;
        }

        protected override void OnAppearing() {

            BindingContext = new TeamDetailsViewModel(_teamId);
            base.OnAppearing();
        }

        private void OnPlayerTapped(object sender, ItemTappedEventArgs e) {

            if (e.Item != null) {

                var player = (Player)e.Item;

                Navigation.PushAsync(new EditPlayerPage(player.PlayerId));
            }
        }
    }
}

ここでは OnPlayerTapped を実装しています。 EditPlayerPage を開きますが、次のステップで説明します。 TeamDetailsViewModel に戻りましょう。

ObservableCollection の下にツールバーアイテム用のコマンドがいくつかあります。

コンストラクターではプライベートフィールドに _teamId をセットし、コンストラクターの外でも使えるようにしています。

再び GetInstance メソッドで Realm オブジェクトを生成しています。

team という名前をつけた新しいオブジェクトはデータベースから作られ、 Find というジェネリックメソッドを使ってフェッチします。

構文はこの通りです。

context.Find<TYPE_OF_CLASS_TO_GET>(PRIMARY_KEY);

team が取得できたら view model のプロパティをセットします。

データベースから取得した observable collection を初期化し、 All メソッドを使用します。 All メソッドは IQueryable を返すので、問題なく LINQ が使えます。 そのチームのメンバーに絞るために Where を使用しています。

最初にこのコードを書いたとき、このような条件をセットしようとしました。

.Where(p => p.Team.TeamId == _teamId)

しかし、コメントに書いたように「ネストした RealmObjects 属性で問い合わせするのは現在サポートされていません」。

その下で、コマンドをいくつか初期化しています。最後に書いてあるオブジェクト削除用のコマンドはこのチュートリアルで説明したいものです。

再び Realm の context インスタンスを生成し、この team を teamIdを引数にした Find メソッドで検索します。

Write メソッドを開く必要があり、その中でとてもシンプルな Remove メソッドを使ってデータベースからオブジェクトを削除することができます。

Remove メソッドはパラメタとして RealmObject をとり、このケースでは team が削除されます。

必要ならばこの部分をテストすることができます(もちろん必要です)。

次のステップでは、 Realm のオブジェクトを Edit / Update する方法を見ていきます。

UPDATE

TeamDetailsViewModel の最後の部分では EditTeamCommand を初期化していました。 EditTeamPage を開き、 teamId の値を渡していることがわかるでしょう。 EditTeamPage は AddTeamPage と同じなのでコード量を減らすために AddTeamPage を使ってみましょう。このケースではそのページを次のように作ります。

EditTeamPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Xamarin.Forms_Realm.Views.EditTeamPage"
             Title="Edit team">

    <ContentPage.ToolbarItems>
        <ToolbarItem Text="Save"
                     Icon="ic_save.png"
                     Command="{Binding SaveTeamCommand}" />
    </ContentPage.ToolbarItems>

    <ContentPage.Content>

        <StackLayout>

            <Entry Placeholder="Team Name/Title"
                   Text="{Binding Title}"
                   Margin="4" />

            <Entry Placeholder="Manager name"
                   Text="{Binding Manager}"
                   Margin="4" />

            <Entry Placeholder="City name"
                   Text="{Binding City}"
                   Margin="4" />

            <Entry Placeholder="Name of the stadium"
                   Text="{Binding StadiumName}"
                   Margin="4" />

        </StackLayout>

    </ContentPage.Content>
</ContentPage>

新しいものはありません。

次に、新しい EditTeamViewModel を作成し、このコードをその内部に配置します。

EditTeamViewModel.cs
using Realms;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;
using Xamarin.Forms_Realm.Models;

namespace Xamarin.Forms_Realm.ViewModels {

    class EditTeamViewModel : BaseViewModel {

        private string title;
        public string Title {
            get { return title; }
            set {
                title = value;
                OnPropertyChanged("Title");
            }
        }

        private string manager;
        public string Manager {
            get { return manager; }
            set {
                manager = value;
                OnPropertyChanged("Manager");
            }
        }

        private string city;
        public string City {
            get { return city; }
            set {
                city = value;
                OnPropertyChanged("City");
            }
        }

        private string stadiumName;
        public string StadiumName {
            get { return stadiumName; }
            set {
                stadiumName = value;
                OnPropertyChanged("StadiumName");
            }
        }

        public ICommand SaveTeamCommand { get; private set; }

        private string _teamId;

        public EditTeamViewModel(string teamId) {

            Realm context = Realm.GetInstance();

            var team = context.Find<Team>(teamId);
            _teamId = team.TeamId;

            Title = team.Title;
            Manager = team.Manager;
            StadiumName = team.StadiumName;
            City = team.City;

            SaveTeamCommand = new Command(SaveTeam);
        }

        async void  SaveTeam() {

            Realm context = Realm.GetInstance();

            var team = context.Find<Team>(_teamId);

            context.Write(() => {

                team.Title = Title;
                team.Manager = Manager;
                team.StadiumName = StadiumName;
                team.City = City;

                context.Add<Team>(team, update: true);
            });

            await Application.Current.MainPage.Navigation.PopAsync();
        }
    }
}

コンストラクターで teamId を取得し、 Id でデータベースからデータを取得し、 view model のプロパティをセットし、 team データの save / update 用のコマンドを初期化しています。ここでは特別なことは行なっていません。

しかし、一方で SaveTeam はとても面白いのです。 SaveTeam メソッドはツールバーアイテムの「 Save 」をクリックした時に呼ばれます。

SaveTeam では Id でオブジェクトを取得します。面白いのは、 Write メソッドを開き、 delegate ないで新しい値を team オブジェクトにセットしていることです。もし Write メソッドの外かつ delegate の内側でセットしようとすると、次の例外が発生します。
Attempting to modify object outside of a write transaction.

そのため、全ての更新操作は Write 内で行う必要があることを覚えておくことが重要です。

新しい値をセットし終えたら変更をコミットする必要があります。 Realm では Add メソッドでデータベースのオブジェクトを更新できますが、 bool 型の2番目のパラメータ 「 update 」はここで何を行うべきかを自明に説明していて、
true に設定するとオブジェクトが更新されます。

テストしてみましょう。

スクリーンショット 2017-08-12 0.19.25.png
(オリジナル記事より)

LGTM

これで Team クラス/テーブルに関して Realm モバイルデータベースを使った CRUD 処理は完成です。

次のステップでは、 Team に外部オブジェクトを追加するのと一緒に、新しい選手を追加したり既存の1人の選手を更新する方法について説明します。これら2つのクラスの1:Nの関係を思い出してください。自分でも挑戦できますし、このチュートリアルを追えば必要な知識は書いてあります。

チームに選手を追加する

チームの詳細ページの中には、新しいプレーヤーを追加するためのツールバーアイテムが1つあります。 TeamDetailsViewModel を開いて AddPlayerPage を開くためのコマンドをセットし、 id に teamId を渡しましょう。上記の gist では実装済みです。

もしまだであれば新しい AddPlayerPage を作成してください。 XAML は下記の通りです。

AddPlayerPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Xamarin.Forms_Realm.Views.AddPlayerPage"
             Title="Add new player">

    <ContentPage.ToolbarItems>
        <ToolbarItem Text="Save"
                     Icon="ic_save.png"
                     Command="{Binding SavePlayerCommand}" />
    </ContentPage.ToolbarItems>

    <ContentPage.Content>

        <StackLayout>

            <Label Text="{Binding TeamName, StringFormat='New player for: {0}'}"
                   Margin="4,12,4,4" />

            <Entry Placeholder="Full name"
                   Text="{Binding Name}"
                   Margin="4" />

            <Entry Placeholder="Position"
                   Text="{Binding Position}"
                   Margin="4" />

            <Entry Placeholder="Jersey Number"
                   Text="{Binding JerseyNumber}"
                   Margin="4" />

        </StackLayout>

    </ContentPage.Content>
</ContentPage>

このページのコードビハインドは下記の通りです。

AddPlayerPage.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using Xamarin.Forms_Realm.Models;
using Xamarin.Forms_Realm.ViewModels;

namespace Xamarin.Forms_Realm.Views {

    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class AddPlayerPage : ContentPage {

        private string _teamId;
        public AddPlayerPage(string teamId) {
            InitializeComponent();

            _teamId = teamId;
        }

        protected override void OnAppearing() {
            BindingContext = new AddPlayerViewModel(_teamId);
        }
    }
}

TeamDetailsPage と同様、 teamId の値を AddPlayerPage に渡し、 BindingContext のセット時に teamId を AddPlayerViewModel に渡しています。

AddPlayerViewModel.cs
using Realms;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;
using Xamarin.Forms_Realm.Models;

namespace Xamarin.Forms_Realm.ViewModels {

    public class AddPlayerViewModel : BaseViewModel {

        private string teamName;
        public string TeamName {
            get { return teamName; }
            set {
                teamName = value;
                OnPropertyChanged("TeamName");
            }
        }

        private string name;
        public string Name {
            get { return name; }
            set {
                name = value;
                OnPropertyChanged("Name");
            }
        }

        private string position;
        public string Position {
            get { return position; }
            set {
                position = value;
                OnPropertyChanged("Position");
            }
        }

        private string jerseyNumber;
        public string JerseyNumber {
            get { return jerseyNumber; }
            set {
                jerseyNumber = value;
                OnPropertyChanged("JerseyNumber");
            }
        }

        public ICommand SavePlayerCommand { get; private set; }

        private string _teamId;
        private Realm context;

        public AddPlayerViewModel(string teamId) {

            _teamId = teamId;
            context = Realm.GetInstance();

            var team = context.All<Team>().Where(t => t.TeamId == teamId).FirstOrDefault();

            TeamName = team.Title;

            SavePlayerCommand = new Command(SavePlayer);
        }

        void SavePlayer() {

            var team = context.All<Team>().Where(t => t.TeamId == _teamId).FirstOrDefault();

            context.Write(() => {

                context.Add<Player>(new Player() {
                    JerseyNumber = Int32.Parse(JerseyNumber),
                    Name = Name,
                    Position = Position,
                    Team = team
                });
            });

            Application.Current.MainPage.Navigation.PopAsync();
        }
    }
}

view model を見ていきましょう。バインドしたプロパティがいくつかあります。また、保存アクションのためのコマンドが1つあります。

コンストラクターでは、このチュートリアルの前のステップから学んだ標準的な方法で context という実際のオブジェクトを作成しています。

主要部分は SavePlayer メソッドです。

最初に id で team をフェッチし、その team に対して新しい選手を追加します。ご存知の通り、 SaveMethod はツールバーのアイテムをクリックしたときに呼ばれます。

71行目(訳注: context.Write(() => { ) では delegate と一緒に Write メソッドを呼び、新しい選手と外部プロパティの Team を追加しています。

アプリを実行し、テストしてみましょう。

スクリーンショット 2017-08-12 10.52.56.png
(オリジナル記事より)

動きました!

最後は選手用の update / edit ページです。

TeamDetail.xaml.cs と同様、 OnPlayerTapped というイベントの中で EditPlayerPage を開き、 playerId を渡しています。

EditPlayerPage は追加アクションを行うページととてもよく似ています。

EditPlayerPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Xamarin.Forms_Realm.Views.EditPlayerPage"
             Title="Edit player">

    <ContentPage.ToolbarItems>
        <ToolbarItem Text="Save"
                     Icon="ic_save.png"
                     Command="{Binding SavePlayerCommand}" />

    </ContentPage.ToolbarItems>

    <ContentPage.Content>

        <StackLayout>

            <Label Text="{Binding TeamName, StringFormat='Playing for: {0}'}"
                   Margin="4,12,4,4" />

            <Entry Placeholder="Full name"
                   Text="{Binding Name}"
                   Margin="4" />

            <Entry Placeholder="Position"
                   Text="{Binding Position}"
                   Margin="4" />

            <Entry Placeholder="Jersey Number"
                   Text="{Binding JerseyNumber}"
                   Margin="4" />

        </StackLayout>

    </ContentPage.Content>
</ContentPage>

コードビハインドです。

EditPlayerPage.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using Xamarin.Forms_Realm.ViewModels;

namespace Xamarin.Forms_Realm.Views {

    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class EditPlayerPage : ContentPage {

        private string _playerId;

        public EditPlayerPage(string playerId) {
            InitializeComponent();

            _playerId = playerId;
        }

        protected override void OnAppearing() {

            BindingContext = new EditPlayerViewModel(_playerId);
        }
    }
}

view model はこの通りです。

EditPlayerViewModel.cs
using Realms;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;
using Xamarin.Forms_Realm.Models;

namespace Xamarin.Forms_Realm.ViewModels {

    public class EditPlayerViewModel : BaseViewModel {

        private string teamName;
        public string TeamName {
            get { return teamName; }
            set {
                teamName = value;
                OnPropertyChanged("TeamName");
            }
        }

        private string name;
        public string Name {
            get { return name; }
            set {
                name = value;
                OnPropertyChanged("Name");
            }
        }

        private string position;
        public string Position {
            get { return position; }
            set {
                position = value;
                OnPropertyChanged("Position");
            }
        }

        private string jerseyNumber;
        public string JerseyNumber {
            get { return jerseyNumber; }
            set {
                jerseyNumber = value;
                OnPropertyChanged("JerseyNumber");
            }
        }

        public ICommand SavePlayerCommand { get; private set; }

        private string _playerId;

        public EditPlayerViewModel(string playerId) {

            Realm context = Realm.GetInstance();

            var player = context.Find<Player>(playerId);

            _playerId = player.PlayerId;

            TeamName = player.Team.Title;

            Name = player.Name;
            Position = player.Position;
            JerseyNumber = player.JerseyNumber.ToString();

            SavePlayerCommand = new Command(SavePlayer);
        }

        void SavePlayer() {

            Realm context = Realm.GetInstance();

            var player = context.Find<Player>(_playerId);

            context.Write(() => {

                player.Position = Position;
                player.Name = Name;
                player.JerseyNumber = Int32.Parse(JerseyNumber);

                context.Add<Player>(player, update: true);
            });

            Application.Current.MainPage.Navigation.PopAsync();
        }
    }
}

このコードはとても明確でしょう。このブログで説明していない新しいことはありません。このサンプルはわたしの GitHub に上げてあります。

https://github.com/almirvuk/Xamarin.Forms_Realm_Example

このチュートリアルが役に立つと嬉しいです。このチュートリアルのゴールは素晴らしい Realm モバイルデータベースを使用した CRUD の例を示すことです。将来わたしも Realm を使用するでしょう。もしわからないことがあればブログ記事のコメント欄にコメントしてください。有用であればあなたも使ってみて、友人に広めてください。

よろしくお願いします!


翻訳後記

英語的な部分は、義務教育やオーストラリアの語学学校で習ったのとは違う感じで厳しかったです。というわけでコード読みながら雰囲気を察して訳した部分も多いので、気になる部分があればぜひ編集リクエスト送ってください。

どこの国の方が書かれたんだろうと思ってよくよく見てみるとボスニア・ヘルツェゴビナの方でした。公用語でないけど英語で書いてくれてほんとありがたかったです。

日本では最近エクセルソフトさんと Realm さんがパートナーシップ契約を結び、これまで以上に有料プランに手を出しやすくなるはずなので色々試してみたいですね!