このリポジトリは、2019年5月29〜30日に東京で開催された 「de:code 2019」のセッション「MW03: Xamarin.Forms アプリケーション設計パターン」 に対応するサンプルコードと解説です。
Xamarin.Forms 3.6 で Xamarin.Forms Visual という機能が追加されました。
- Beautiful Material Design for Android & iOS | Xamarin Blog
- Xamarin.Forms Visual - Xamarin | Microsoft Docs
これは Xamarin.Forms Visual を使用して Material デザインを適用した Android/iOS 向けのサンプルアプリケーションです。
Xamarin.Forms Visual は、 ContentPage
や各UIパーツに存在する Visual
プロパティに定義された値を指定することによって、そのUIパーツの 見た目や挙動を切り替える 機能です。
公式で用意された、Visual プロパティへ設定可能な値には、
Default
MatchParent
Material
があり、Material は、Visual への設定値の一つです。
Page
や各種View の Visual
プロパティに Material
を設定することで、対応したUIパーツが Material デザインになります。
Visual の実態は Custom Renderer であり、Material
と設定された場合には、Material 用の Custom Renderer が動作し、Material デザインのような見た目と挙動を実現しています。
見た目だけを切り替えるのであれば Themes や Styles という機能が既に存在していますが、「挙動」も含めた柔軟な UI の変更が求められる場合には Visual を使用すべきです。
- Windows - Visual Studio 2017 または 2019
- macOS - Visual Studio for Mac ver 8.0.5 (作者はこちらを使用しています)
- Android - Android 5.0 以降の実機端末またはエミュレータ
- iOS - iOS 8.0 以降の実機端末またはシミュレータ
※UWP には対応していません。
Visual Studio 2017/2019 または Visual Studio for Mac で XamMaterialTodo.sln
ファイルを開き、実機またはエミュレータをデプロイ先に選択して実行してください。
さて、この Visual Material に触れるサンプルとして用意したのが、簡単な ToDo アプリケーションです。
- ToDo の一覧画面で、上から優先順位の高い順、期限の近い順に表示されます
- 右上のボタンで、完了した ToDo を隠すかどうかを切り替えられます
- 右下のボタンで、新しい ToDo を追加できます
- iOS ではスワイプ、Android では長押しでコンテキストメニューが表示でき、ToDo の削除と未完了に戻す事ができます。
- ToDo のタイトル、期日、優先順位、詳細が登録できます
- 右下のボタンで ToDo を「完了」にできます
アプリに簡単にマテリアルデザインを適用できる、という触れ込みの Xamarin.Forms Visual Material ですが、実際には満足が行く程度にまでマテリアルな見た目にするには、アプリケーションでの実装を「がんばる」必要があります。
このサンプルアプリケーションも、「そんなにマテリアルか?」と言われると「…はい」としか答えられないです。。。
それは、現在対応 Material にしているUIパーツは以下の11個(*付きはサンプルアプリで使用しています)だけであり、これらだけでは「Material なアプリ」を作ることは難しいためです。
- Button
- Entry(*)
- Frame(*)
- ProgressBar
- DatePicker(*)
- TimePicker
- Picker
- ActivityIndicator
- Editor(*)
- Slider(*)
- Stepper
Material Design 公式で提示されている Components や、マテリアルデザインの提供者である Google が開発しているクロスプラットフォームアプリ開発ツール Flutter の Material Widgets と比較すると、パーツの数も再現度も貧弱と言わざるを得ません。
Custom Renderer を自作するか、既存の部品を組み合わせてそれっぽく見せるしかないです。
例えば、FAB(Floating Action Button) は、このサンプルでは、ただの丸い ImageButton
です。
<ImageButton Grid.Column="1" Grid.Row="1"
Source="https://raw.githubusercontent.com/amay077/XamMaterialTodo/master/img/baseline_add_white_48dp.png"
BackgroundColor="#2B78FE"
Padding="10" CornerRadius="25"
WidthRequest="50" HeightRequest="50"
VerticalOptions="Center" HorizontalOptions="Center"
Command="{Binding AddCommand}"/>
「もっと Material デザインにしたい!」という方は、2019年3月末に Xamarin チームが「Xamarin Visual Challenge」というオンラインイベントを行っていました。著名なアプリのデザインを Xamarin Forms を使って再現してみよう、という趣旨で、その結果を GitHub のリポジトリで見ることができます。
- Join the Xamarin Visual Challenge | Xamarin Blog
- Visual Challenge Conquered! | Xamarin Blog
- Pull Requests · davidortinau/VisualChallenge
投稿された Pull Requests を見てみると、どのようにして Material デザインを再現したかが分かりますので、参考にしてみてください。私は「皆さん、自力で頑張ってるなあ」という感想を持ちました(Visual Material あまり関係なくない?ともw)。
2019年5月29日に Xamarin.Forms 4.0 がリリースされ、Shell という機能が公式に提供されました。
- Welcome to the Shell Era: Xamarin.Forms 4.0 Released | Xamarin Blog
- Xamarin.Forms Shell - Xamarin | Microsoft Docs
Shell とは、スマホアプリでよく使用される画面パターンをフレームワークとして提供し、その画面パターンが適用できるのであれば高速に「モダンな」アプリが開発できる、というものです。
Visual とは直接関係はなく、Shell のソリューションテンプレートでも Visual は使用されていませんが、Shell と Visual を併用すると、「より Material でモダンな」アプリが作りやすいものと思います。例えば、このサンプルアプリの iOS 版はヘッダが白い背景色ままですが、Shell で作ったアプリの iOS 版は、Android 版と同じくヘッダが青い背景色になります。
Xamarin.Forms 3.5 で FontImageSource
が導入され、 FontAwesome や Material Design Icons が利用しやすくなりました。
Xamarin.Forms 4.0 では、あらゆるコントロールですべての ImageSource が使用できるようになりました。
例えば Button.Image
プロパティは、これまでは FileImageSource
であったために、FontImageSource
, UriImageSource
などは使用できませんでしたが、Xamarin.Forms 4.0 からはすべて使用できます。
このサンプルアプリでも、アイコンは Material Design Icons と FontImageSource
を使用しています。
<ImageButton Grid.Column="1" Grid.Row="1"
x:Name="btn"
BackgroundColor="#2B78FE"
Padding="10" CornerRadius="25"
WidthRequest="50" HeightRequest="50"
VerticalOptions="Center" HorizontalOptions="Center"
Command="{Binding AddCommand}">
<ImageButton.Source>
<FontImageSource
FontFamily="{DynamicResource MaterialFontFamily}"
Glyph="{StaticResource plus}" />
</ImageButton.Source>
</ImageButton>
WebFont(.ttf
)をプロジェクトへ追加する方法は、
、FontImageSource
の使い方については、
が、それぞれ詳しいです。このサンプルと合わせてご覧ください。
ここからは Visual Material に関係のない、GUI アプリケーション設計の話です。
このサンプルアプリケーションは MVVM パターンを採用しています。DDD や Clean Archtecture から「Usecase」や「Repository」という概念も採用しています。
de:code で発表された @runceel さんによる登壇内容(以下に詳細解説あり)と、大筋では変わらない設計になっているので、参考にしていただければ幸いです。
尚、プラットフォーム側での固有処理は行っておらず、共通の XamMaterialTodo プロジェクトですべての実装を行っています。
XamMaterialTodo プロジェクトのクラス図は以下のようになっています。
また、ディレクトリ(名前空間)構成は以下のようになっています。
/XamMaterialTodo
├/DataModels
├/Repositories
├/Usecases
└/Presentations
├/Main
└/Detail
プロジェクト共通で使用するデータクラスが含まれています。今回は一つの ToDo を表す TodoItem
のみが含まれ、あらゆる箇所で使用されます。
データストアから TodoItem
読み出し、または保存する Interface 定義とその実装クラスを含みます。
今回はデータストアに LiteDB を採用しました。
端末内のデータストアといえばまずは SQLite が想定されると思いますが、SQLite はテーブルを設計・作成したり、データのI/Oのために SQL を記述する必要があるなどの面倒さがあります。
LiteDB は、MogoDB のようなドキュメント指向の NoSQL で、データクラスである TodoItem
をそのまま扱える利点があります。またすべて C# で実装されていて依存ライブラリが少なく、導入も簡単です。
LiteDB に対してのデータIOは LiteDbTodoRepository
として実装されています。
もし、SQLite, Firebase Firestore, AppCenter Data といった他のデータストアに対応したい場合は、 ITodoRepository
インターフェースを実装して新しいリポジトリクラスを作成し、LiteDbTodoRepository
と差し替えるだけです。
この層にはToDoアプリについてのビジネスロジックを実装したクラスが含まれます。
今回は機能の少ない単純なアプリであるため、TodoUsecase
クラスが一つだけあり、「ToDo の追加や削除」、「ToDo の完了」、「未完了または全ての ToDo 一覧の取得」などの機能が実装されています。もちろんその実装には ITodoRepository
が使用されています。
この層には、いわゆる MVVM の V(View) と VM(ViewModel) が含まれます。
サブディレクトリ Main
は ToDo 一覧画面、 Detail
が ToDo 詳細画面を示し、それぞれのディレクトリに画面を示す Page
クラスと、 ViewModel
クラスが含まれます。多くの人は Viws
と ViewModels
でディレクトリや名前空間を分けると思いますが、View と ViewModel はペアで密結合しているため、画面ごとにディレクトリを分けてみました。1
Page とのデータバインディングに必要な ViewModel の INotifyPropertyChanged の実装は ReactiveProperty を採用しています。
Reactive Extensions については、使い倒してはいませんが、DetailPageViewModel
にていずれかの入力項目が変化した時、「変更されたバージョンの TodoItem
を作り直す」という処理で活用しています。
// いずれかの項目が変化したら TodoItem を作り直す
UpdatedItem = Observable.CombineLatest(
Title, Description, Priority, HasDueDate, DueDate,
(title, description, priority, hasDueDate, dueDate) =>
new TodoItem(item.Id, title, IsDone.Value, description, priority,
hasDueDate ? dueDate : (DateTimeOffset?)null, item.CreateDate))
.ToReadOnlyReactiveProperty();
コピペだけで作ると後で痛い目を見るかも、という点を挙げてみました。
ViewModel から画面遷移を行う方法は、今回は、
- ViewModel で画面遷移リクエストイベントを発生
- Page でそれを受信して画面遷移
という方法を採用していますが、中〜大規模なアプリケーションな場合は、Prism などのフレームワークを使ってその仕組みに従った方が良いです。
- 上記の画面繊維で、Page でのイベント受信 (
MainPage.xaml.cs
など) - ViewModel 内での、
IObservable
のSubscribe
(DetailViewModel.cs
など)
これらの箇所では、イベントの -=
での登録解除や、Subscribe()
の戻り値である IDisposable
の .Dispose()
を呼ぶべきかについて、注意を払う必要があります。
TodoUsecase
では、 ReactiveProperty<T>
よりも ReactivePropertySlim<T>
を使用した方が良いでしょう。xxxSlim の方が、シンプルで軽量です(ReactivePropertySlim詳解 - neue cc)。
Flutter や React の開発ツールは、コードを変更すると実行中のアプリケーションにすぐにそれが適用される、いわゆる「HotReload」という仕組みが用意されています。
一方、Xamarin では、Visual Studio に搭載されている "XAML Previewer" を通常は使用します。
しかし XAML Previewer は Android や iOS の画面をシミュレートしているため、プラットフォームに依存したUI部品は描画されなかったり、Previewer 自体が機能しなくなったりとあまりストレスの軽減になっていないと個人的には感じます。
このサンプルでは、Xamarin.Forms でも HotReload を実現する LiveXAML というツールに必要な(nuget)ライブラリを同梱しています。
Visual Studio に LiveXAML の拡張機能をインストールして、アプリを実行すると、xxx.xaml
を変更して保存すればすぐに実行中のアプリに反映されます。有償ツールですがトライアル期間があり、それが切れても xaml ファイルが3つまでなら使用できますので、よければ試してみてください。私は LiveXAML で、画面開発の快適度が格段に上がりました。
See LICENSE
このサンプルについての質問は Issues へ、不具合の修正やその他ご指摘などは Pull request をお送りください。
Footnotes
-
これは DroidKaigi2019 アプリの構成 を参考にしています。 ↩