Xamarin.FormsでRx + Fluxでの実装をやってみる

この記事は、[[学生さん・初心者さん大歓迎！]Xamarin Advent Calendar 2016](http://qiita.com/advent-calendar/2016/xamarin-student) の9日目の記事です。

---

## <i class="fa fa-cube" style="font-size:1em;"></i> はじめに

Androidなんかでは、アプリ設計をどのようにするかは結構白熱した議論がされており、MVPだMVCだCleanArchitectureだみたいな感じでビシっと決まっていないのが現状です。
一方で、Xamarin.FormsではMVVMを用いるのがほぼスタンダードとなっており、[Prism for Xamarin.Forms](https://www.nuget.org/packages/Prism.Forms/) や [MVVM Light Toolkit](https://mvvmlight.codeplex.com/) といったライブラリによって**MVVMでやれば楽**という環境が整っています。

この記事では、そんな中でなんとか**ReactiveExtensions(Rx)+Fluxを使っていい感じにできないか**というのを実践してみた記事になります。

## <i class="fa fa-cube" style="font-size:1em;"></i> Rxとは

ReactiveExtensions(Rx)は、C#のLINQを更に進化させたすごいやつです。
C#では結構知名度はあるとは思いますが、最近ではRxJavaやRxSwiftといった各言語へのポートが充実し始め、熱が高まっています。

> こわくないReactive Extensions超入門
http://qiita.com/acple@github/items/6cfee916f09632037a6e

## <i class="fa fa-cube" style="font-size:1em;"></i> Fluxとは

http://facebook.github.io/flux/

Facebookが提唱しているUI設計のためのアーキテクチャの１つです。

![c730efea-a42f-4ba7-931d-3acc8919ac32.png](https://qiita-image-store.s3.amazonaws.com/0/29459/9b6b321d-f4e7-da03-1640-02e86bf49ac1.png "c730efea-a42f-4ba7-931d-3acc8919ac32.png")

もともとはWeb向けに提唱されているものです。
データが1方向にしか流れなくなるので、流れが把握しやすくなります。

http://qiita.com/syossan27/items/7e1b2e07ac68b96bdaa7

Rxと非常に相性がいい（個人的感想）です。

## <i class="fa fa-hand-o-right" aria-hidden="true"></i> 解説の前に

今回は、以下のAndroid向けの導入記事をかなり参考にさせていただいております。

> RxJava + Flux (+ Kotlin)によるAndroidアプリ設計
http://qiita.com/satorufujiwara/items/cbf304891daec87ba5b7

コードや構成なども比較的似ているので、Androidわかる方は見ておくといいかもしれません。

## <i class="fa fa-github-alt" aria-hidden="true"></i> 作ったもの

Githubユーザー名を入力してもらい、Getボタンをおすことでリポジトリ一覧が表示されるものです。

![device-2016-12-09-120325.png](https://qiita-image-store.s3.amazonaws.com/0/29459/0647b9c1-5024-ad43-1c5d-224e04632dfc.png "device-2016-12-09-120325.png")

今回のソースコードを置いているリポジトリはこちらです。

https://github.com/r-ralph/XamarinFormsFlux

## <i class="fa fa-cube" style="font-size:1em;"></i>　必要なNuGetパッケージ

- [Microsoft.Net.Http](https://www.nuget.org/packages/Microsoft.Net.Http)
	- ネットワーク通信
- [Newtonsoft.Json](https://www.nuget.org/packages/Newtonsoft.Json)
	- JSONのパース
- [PCLStorage](https://www.nuget.org/packages/PCLStorage)
	- SQLiteのデータベースの保管
- [sqlite-net-pcl](https://www.nuget.org/packages/sqlite-net-pcl)
	- データベース
- [System.Reactive](https://www.nuget.org/packages/System.Reactive)
	- ReactiveExtensions(Rx)
- [Unity](https://www.nuget.org/packages/Unity)
	- Dependency Injection

## <i class="fa fa-cube" style="font-size:1em;"></i>　名前空間

```
XamarinFormsFlux
 ├─Data : 外部APIとのやり取り
 ├─Db : SQLiteのコネクションなど
 ├─Model : モデル
 ├─Ui : PageやAction, Store, Dispatcherなど
 └─Util : ユーティリティー

```

## <i class="fa fa-cube" style="font-size:1em;"></i>　Unityに関して

Unity（ゲームエンジンじゃない方）は、Xamarin上で使用できるDIコンテナです。
XamarinにはすでにDependencyServiceという依存性注入の仕組みがありますが、今回の用途では使いにくい部分が多かったので、こちらを採用しました。
詳しくは以下にまとめられています。

> Xamarin の救世主 Unity!
http://qiita.com/matatabi_ux/items/b7fc1cf828bfe84364cd

今回は、MainComponent.csにおいて、依存性解決を行うUnityContainerを生成、型の登録を行っています。
後述のMainActionやMainStoreなどは、全てDIで生成させています。

## <i class="fa fa-cubes" style="font-size:1em;"></i> クラス解説

### <i class="fa fa-file-code-o" aria-hidden="true"></i> MainPage.xaml.cs

今回表示させるPageです。

コンストラクタ内でMainComponent経由でMainStoreとMainActionを生成しています。
MainComponent自身は遅延生成されるようになっています。

```MainPage.xaml.cs
Lazy<MainComponent> component = new Lazy<MainComponent>(() => Init());

public MainStore mainStore { get; set; }
public MainAction mainAction { get; set; }

public MainPage()
{
	InitializeComponent();
	component.Value.Inject(this);
}
```

`InitData`関数内で、Storeから各種データの取得を行います。
データはRxのObservable上に流れ、何かしら変更があった場合はこのObservableにデータが流れてくることになります。
流れてきたデータをViewに対して表示させるか、何かしらのイベントを発火させるかのみにします。

```MainPage.xaml.cs
void InitData()
{
	mainStore.Repos()
			 .Subscribe((items) =>
			 {
				 Device.BeginInvokeOnMainThread(() => { listView.ItemsSource = items; });
			 });
	mainStore.Errors()
			 .Subscribe((exception) =>
			 {
				 Device.BeginInvokeOnMainThread(() => { listView.ItemsSource = exception.ToString(); });
			 });
	mainStore.Name()
			 .Subscribe(async (name) =>
			 {
				 await mainAction.Refresh();
			 });
}
```

`InitListener`関数内では、ボタンクリックイベントに対してActionを呼び起こすだけに留めます。

```MainPage.xaml.cs
void InitListener()
{
	button.Clicked += async (sender, e) =>
	{
		await mainAction.Name(entry.Text);
	};
}
```

Page内で行っているのは「何かしらのイベントが起きたらActionを呼ぶ」ことと、「データがやってきたら反映させる」の2つの動作の登録のみです。


### <i class="fa fa-file-code-o" aria-hidden="true"></i> MainAction.cs

Actionとして用意されているのは「アイテムをAPIから取ってくるRefresh」と「取得させるユーザー名を変更させるName」
共に、作業の結果はDispatcherに対して投げられます。

`Refresh`関数では、APIからデータを取得した上で、データベースに追加する作業を行います。

```MainAction.cs
public async Task Refresh()
{
	await Task.Factory.StartNew(() =>
	{
		dispatcher.nameSubject
				  .Take(1) // 取得するべきユーザー名を取得する
				  .SelectMany((name) => { return repository.GetRepos(name); }) // リポジトリから取得する（RxのFlatMapを使用している）
				  .Subscribe((items) => {	 dispatcher.Dispatch(items); }, // DBにデータを投げる
				  			  (e) => { dispatcher.errorSubjecct.OnNext(e); }); // エラーだったとき
	});
}
```

`Name`関数は、受け取った文字列をそのままDispatcherへ渡します。
（ここでは、RxのSubjectを使っています。詳しくはDispatcherのところでやります。）

```MainAction.cs
public async Task Name(string name)
{
	await Task.Factory.StartNew(() => {	dispatcher.nameSubject.OnNext(name); });
}
```

Action内では、イベントに応じて、データを作成する機構を提供します。

### <i class="fa fa-file-code-o" aria-hidden="true"></i> MainStore.cs

次は、Pageでデータの取得口となるStoreについて見ていきます。
しかし、StoreでやっているのはDispatcherで提供されるObservable/Subjectを返しているだけです。

``` MainStore.cs
public IObservable<IList<Item>> Repos()
{
	return dispatcher.Repos();
}

public IObservable<string> Name()
{
	return dispatcher.nameSubject.AsObservable();
}

public IObservable<Exception> Errors()
{
	return dispatcher.errorSubjecct.AsObservable();
}
```

それでは、ActionとStore両方から見られているDispatcherとは何者なのか見ていきましょう。

### <i class="fa fa-file-code-o" aria-hidden="true"></i> MainDispatcher.cs

MainDispatcherでは、主に以下の4つの機能があります。

- 取得させるユーザー名を保持する
- エラーを保持する
- DBに対してAPIから取得したデータを保管させる
- DBに変更があったらユーザー名にマッチするデータを流す

この内、上2つの保持する部分には、Rxで提供されるSubjectを使用しています。
Subjectに関しては、自分の書いた記事を見ていただければと思います。

> Rxで知っておくと便利なSubjectたち
http://qiita.com/ralph/items/f7205c8171826cc2153b
(記事はRxJavaに関してですが、基本の操作は同じです)

今回、nameSubjetは`BehaviorSubject`、errorSubjectは`Subject`(RxJavaでのPublishSubject)です。

MainActionの`Name`関数でこの`nameSubject`に`OnNext`で新しい名前を渡し、MainStoreでは`Name`関数がObservableとしてSubjectを返します。

それをMainPage内でSubscribeしているので、結果としては**MainPageのボタンをクリックすることで`mainAction.Refresh`関数が呼ばれる**ということになります。

```フロー
ボタンがクリックされる
↓
MainPage#mainAction.Name()が実行される
↓
MainDispatcher#nameSubject.OnNext()が実行される
↓
(MainDispatcher#nameSubjectをMainStore.Name()でObservableとして返している)
↓
(そのObservableをMainPageないでSubscribeしている)
↓
mainAction.Refresh()が実行される
```

---

下2つの機能に関してはDBが絡んできます。

まず、指定したユーザーのアイテムのリストを取得するための`Repo`関数についてです。

```MainDispatcher.cs
public IObservable<IList<Item>> Repos()
{
	return triggers.AsObservable() // triggersにOnNextされるたびに発火する
				   .SelectMany(_ => nameSubject.Take(1)) // 最新のユーザー名を取得
				   .Select((name) =>
			{
				IList<Item> items = new List<Item>();
				using (SQLiteConnection connection = db.CreateConnection())
				{
					foreach (Item item in (from x in connection.Table<Item>() where x.UserName == name orderby x.Id select x))
					{
						items.Add(item);
					}
				}
				return items;
			});
}
```

triggersというSubjectをSubscribeしており、このtriggersが発火するごとに最新のユーザー名を取得してその名前でDBへアクセスしています。

このtriggersへのOnNextを呼び出しているのが、データ保管を行う`Dispatch`関数です。

```MainDispatcher.cs
public int Dispatch(IList<Item> items)
{
	int count = 0;
	using (SQLiteConnection connection = db.CreateConnection())
	{
		connection.BeginTransaction();
		foreach (var i in items)
		{
			count += connection.InsertOrReplace(i);
		}
		connection.Commit();
		triggers.OnNext("");
	}
	return count;
}
```

引数として保管するアイテムのリストを渡すことで、DBに保管してくれます。
そして、DBへのトランザクションの終了と同時に`triggers.OnNext("");`を呼び、Reposの方の`triggers`を発火させます。

この`Dispatch`関数は、MainActionの`Refresh`関数で呼ばれているので、


```
MainActionのRefreshを呼ぶ
↓
APIから取得
↓
DBに保存
↓
ReposのObservableが発火
↓
MainActionのReposをSubscribeしているところにデータがやってくる
```

という一連の流れが実現されています。

この仕組みは、上で紹介したAndroidの方のサンプルで使用されているSQLBriteというライブラリで採用されているテーブルの更新通知システムを簡易的に模倣したものです。

> SQLBriteがテーブル変更を通知する仕組み
http://qiita.com/ara_tack/items/6d1b75ab28e86a8b1477

## <i class="fa fa-exclamation-circle" aria-hidden="true"></i> 問題点

Action→Dispatcher→Store→Viewの流れは上のようにすることで実現できました。

しかし、おそらくC#でのRxに慣れていなかったり、設計そのもののせいで色々問題点があります。

### <i class="fa fa-exclamation-circle" aria-hidden="true"></i> 問題1

**Dispatcher→Actionのデータの流れが含まれている**

コードを見ていてあれ？と思った方もいるかもしれませんが、取得するユーザー名を入力し、それに対応するリポジトリ一覧が返るように実装したところ、MainAction内でDispatcherのSubjectから値を取り出す必要が出てしまいました。

### <i class="fa fa-exclamation-circle" aria-hidden="true"></i> 問題2

**Thread本当にこれでいいのか問題**

本来、Rxには`SubscribeOn`と`ObserveOn`という関数で実行するスレッドを切り替える機能があります。

> SubscribeOnとObserveOnの使い分け
http://qiita.com/Temarin/items/a0525d73e8981489dd3e

しかし、何故かすべてがUIスレッド上で実行されてしまうため、Actionの関数をすべて`async`にすることで強制的にスレッドを移動させ、Subscribeした中で`Device.BeginInvokeOnMainThread`を使うことでUIスレッド上での実行を行っています。

## <i class="fa fa-cube" style="font-size:1em;"></i> まとめ

- 素直にMVVM使ったほうが楽だと思います。
- しかしチャレンジしたい方は採用するのもアリだと思います。
- Rxすごい！便利！

## <i class="fa fa-info-circle" aria-hidden="true"></i> 参考にさせていただいたサイト

Xamarin.FormsでSQLiteをPCL内で完結して使う方法
http://www.nuits.jp/entry/2016/06/27/191636