Xamarin.Formsでアプリリリースしてみてあれこれ

序文

こちらはXamarin Advent Calendar 2016 - Qiita20日目の記事です。
今年はかかわっている会社の方でHS証券様の取引ツールのスマホアプリをXamarin.Formsで作成させていただいてリリースしましたのでそのあたりの話を詳しく。というかほかにXamarinがらみのことやってないので話すネタがないとも言います。
今回作ったのは株式売買などの証券取引などを行う証券会社のお客様が使うアプリです。証券会社のお客様が株を買ったりドルの値段を見たり、自分の取引口座の持ってる株を見たり、銀行との入出金などを行えるものです。
このアプリはパッケージとして横展開をしていく予定で、パッケージとしての共通部分とそれを利用する会社ごとのコードが組み合わさって一つのアプリとなるようにしています。
App.xamlなどで定義した色やスタイルなどを変えたり独自のビューを差し込んだりしてその会社のアプリとして特徴が出るようにする予定です。

アプリ全体の構成

全体の構成としては以下のような感じになります。
図の左側はプロジェクトの構成、右側は各々のプロジェクトの中で実装され動いてるもののイメージです。

会社別のプロジェクトと共通プロジェクト群が一つのアプリを構成します。なるべく多くの部分を共通部分にあげ、その他独自の部分を会社ごとのプロジェクトに残していきます。

プロジェクト構成としては以下のような感じになります。

  • VMから上のロジック層やサービス層。F#PCL。部分によって5,6個に分かれてるけど割愛。
  • 各社共通UI部、共通UI寄りスタートアップロジックなど。C#。OS共通部はPCL、OS個別部は各OS用のライブラリプロジェクト。
  • 各社個別部。アプリ本体、各社非共通UI、ロジックなど。C#。Xamarin.Formsテンプレで作るPCLと各OSのプロジェクト。

内部の構造としては上の方から

  • サービスとして動くロジック。単独もしくは他のサービスと連携して動く。基本的にアクターとして動いてる。
  • 各画面のVM部。ものによってはVMとModelとを分けたりしてますが、わけなくてもよさげなものはファットVMとしてる。異論は認める。
  • UI関係を取りまとめるViewManagerとそのHandlerとしてのTabbedPage継承クラスなど含むUIフレーム部。その上に乗っかるView部。ViewはVMをもとに生成されNavigationやPopやOverwrapなどとして表示される。
  • サービスの開始や初期ビューの表示などを行うスタートアップ部。サスペンド・最下位処理などもここの予定(未実装)。

開発の進め方

スマホ側の開発体制は以下のような感じでした。

  • 自分:全体のフレームワークなどと発注系周り。F#、C#、XMAL。
  • 開発者A:その他のビューの多くの原型。F#、C#、XMAL。
  • 開発者B:後からAが作った原型を引継ぐ。F#はまだ不慣れかも。F#の調整とC#、XMAL。
  • 開発者C:主にアイコンやXAMLなどのレイアウトを担当。少しC#
  • そのほか:プロダクトオーナー兼マネージャーやテスターさんなど。

このほかにサーバー側実装部隊などがいます。

開発にあたっては次のように進めました。

  • 先に検証を兼ねて全体的なフレームの原型を自分が作って想定した構造で動くミニマムなものを作成。その上にサービスやビューを個々の開発者が増築。
  • 開発においては基本的に個々のVMとViewの組み合わせ、それと関連するサービスだけを考慮すればいい。サービス間の連携も初めから大体こんなもんだろうと決めてあったので個々の開発者が作ったものの調整はあまりなかった。
  • サービスやVMととりあえず表示して機能するViewを個々の開発者が作成、大体動いたらCに回し画面をきれいにしてもらうなどとして分担。

各社向けに並行して開発・修正・リリースなどをしていくのでGitでブランチを分けつつ、適当なタイミングで別の会社向けにいじったものなどを統合してパッケージ部分の進化と個々の会社の部分の開発と仕様変更を行っていってます。今のところそれほど困ってないですがこのパッケージを使ってくれる会社様が増えてきた場合はもう少し検討が必要かもです。

UIフレーム

UIフレーム部分だけ切り取るとこんな感じに。

UIのフレームとしてはVM側で次の画面に遷移したいときに、次のVMを作成してナビゲーションプッシュとして遷移したい、ポップアップ表示したい、オーバーラップで半透明な表示したい、今の画面を閉じてあるタブを開きその中のある画面が表示されるようにしたいなどを行えるようにしています。それをうけてビュー側でそのメッセージに合うような表示をし、そのほか画面ごとの回転制御やサスペンド・レジュームなどをおこないます。

UI表示などのフレームとしてPrismなど人気だと思いますが、作成してた頃はPrismまだ出来立てほやほやだったり要件に合わないところもあるだろうし、前にMvvmCrossを使って起動速度改善とかいろいろこうしたいというときにどうにもならなかったりでフレームワークを使うことに懲りたので自前で作りました。面倒なところはありましたが、自前で作ったおかげでいろいろとゴニョゴニョできるので変な要件が来ても困らないというメリットがが。
ビューの表示の仕方で最悪ゴリゴリXamairn.FormsでいうViewの部分をアニメーション絡めて表示してタブを切り替えたりナビゲーションしたりポップアップしてる風に見える仕組みを作れば何かトラブっても逃げられると思っていて、結果想定通りにそれで逃げることになりました。

今回の要件の中で、

  • 半透明表示
  • 発注時などに上からピロッと出る通知ビュー
  • どの画面よりも前にポップアップとしてでないといけないビュー
  • タブビューに表示されるものとその他に押し込まれるものの表示ビューのカスタマイズ
  • VMの取り扱いたい方法にあったサスペンドリジュームの実装
  • Android側、Xamarin.FormsのTabbedPageが要件に合わなかったので自分で全部描画する方法に変更

などのあたりは自前フレームワーク作ったので割とはまることなく対処できました。フレームワーク使ってたらこういうことしたいけれどやり方が分からないということが多く困ったのではないかと思います。けどこの辺は自前実装するコストと便利さの兼ね合いでしょうから便利なPrismを選んだ方が適切な場合も多々あると思います。

UIフレーム動作

VM側で処理に応じて次のVMを作成、表示の仕方含むViewMessageとして投げます。
コードとしては次のような感じです。

//SettingVMを作成、それをNavigationPushとして表示するCommand
let _settingCmd=toEverCmd(fun _->postVMsg<|VMShowAsNavPush (SettingVM()))

//今Overwrapとして表示してるViewを閉じたら作成した発注パネルVMPopupとして表示する関数
let _toOrderPanel(cmdy, side, cMrgn) = 
    postVMsg <| VMCloseOverwrapFromContentThen (VMShowAsPopup(OrderPanelsUtil.toOrderPanelVM cmdy ticket side cMrgn None))

このようなメッセージをViewManager側で受け取りVMに応じたViewを作成、それをNavigationやPopup、Overwrapなどの表示方法に応じた形で表示します。
ViewManagerはメッセージを受けてIViewHandlerを実装したクラスに具体的に表示命令を出します。
最初はTabbedPageを継承したクラスがそれを担ってNavigationPageのPushAsyncやNavigation.PushModalAsyncなどで表示などしていましたが、先にも述べたようにAndroid側はAppCompat絡めると要件に合わないところが出てきたので全部自前で表示する方法に変更しました。
具体的にはTabのような表示もNavigationPush、Popup、Overwrapな表示も全部PageViewの上のGridの上で適当にアニメーションを絡めて表示します。この辺はもっとAndroidっぽく表示しろなどの要件などがなかったのでできたところかもですが。
こんな感じに動いてます。

コード量とプラットフォームごとの共有率

JXUGC #17 お前の Xamarin アプリを見せてみろ! - connpassで話した証券取引アプリとNote app作ってみたからの抜粋ですが、

という感じで93%ほど共有しています。F#はコード密度が高いから実質共有率はもっと高いんだと補正しなくてもよく共有できてるのではと思っています。

苦労したところ

まぁXamarin.Formsでの書き方わからなかったりバグがあったりなど色々はまってた気がしますが、次の辺りは割と大きめにはまって一時期はこれはやばいと思ったことがが。

  • 上に述べたようにFormsのAndoroidのTabbedPageが要件に合わない。Androidよく知らないしForum見てもなんか根深そうで解決できなそう\(^o^)/オワタ->自前描画に書き換え。
  • Androidのパフォーマンスが遅い。なんか全体がもっさりしてるからこっちで何かやっても仕方なさそう。起動が糞遅い。\(^o^)/オワタ->AOTをかけたらなんか速くなったお!->SkiaSharpのDLLがAOT出来ない。エラーになる\(^o^)/オワタ->のでSkiaSharp使ってた部分をAndroid.Graphicsで書き直ししてAOTかけた。
  • メモリ沢山食いすぎ。そもそもXamarin.Formsのアプリ自体メモリ食ってる。メモリリークの原因潰したいけどProfilerがバージョンが上がらずなんか使えない。出力ファイルを解析できるMonoのHeapShotのライブラリを利用して何が参照をつかんでしまってるか見るミニツール作る->Profilerのバージョンアップでファイルフォーマットが変えられたので動かなくなる\(^o^)/オワタ->HeapShotフォークしてCのソースのコメント見ながらエスパーしつつ新しいフォーマット見れるように改造、解析ツール使い続ける⊂(^ω^)⊃ セフセフ!!->Profiler1.0になったけどちらっと試したら参照元たどるの実装されたらしいんだけどどう使ったらいいのかわからない。またフォーマット変えられてないか試してない<−今ここ。
  • ボタンが押されたらその場所その時に応じたピッカーを表示という要件が、Xamarin.Formsのピッカーでやろうとしたけれどどうしてもうまく動かず。なんかゴニョゴニョしたら動いたっぽいのでそのまま触らんとことしてたけどリリース直前になってまた変な挙動し始める\(^o^)/オワタ->最後はXamarin.Formsのソース見てレンダラーなどでごりっと書いてあっさり動く。ソースあるのだから最初からそうしてればよかった。

まとめ

こうして振り返ってみるといろいろあったような気もしますが、今はのど元過ぎてすべてが懐かしい…
結論としてはJXUG17でみなXamarin.Forms使ってたようにXamarin.Formsは使えるいい子なのでみんな使うといいよという感じです。
XamarinもXamarin.FormsもF#もいいぞと(´・ω・`)

話は全くそれますが、途中のポンチ絵はJXUG17でXamarinじゃないストアアプリを見せてチートに一位をもらった時のNoteアプリで作ってます。
ストアアプリ8.1で作ってますが使ってみたいという奇特な方いましたら@omanukeまで連絡ください。Dropboxのリンクを送るのでその中のPowerShellスクリプトをたたいてもらえれば動かせると思います。
自分は慣れてて自然回避してましたがあっちこっちバグあったり怪しい挙動をする代物ですが…出来る出来る詐欺のクラウド経由の同期機能も近々動きそうな気がしないでもないです。
よろしくお願いします。