DroidKaigi2017アプリをベースにXamarin Android開発を考えてたら2か月以上の月日が経過してました(´・ω・`)
はじめに
ジメジメした時期に色々と(´Д`)ハァ…な事が多い日本のXamarin界隈ですが皆さんいかがお過ごしでしょうか?
今回はXamarin Android開発に関して勉強してみたり考えてみた事をジメジメ書き綴ってみようかと思います。
主に頭の中の整理用の記事ですので大多数の方には( ゚Д゚)ハァ?みたいな内容です。
DroidKaigi2017
DroidKaigi2017 凄く良いイベントでした。
本当にキッチリ技術にフォーカスしていて1プログラマとして色々なセッションを聞けてとても面白かったです。
ただ私自身がAndroidアプリを作った経験が少なく分かる事が限られてしまっていたのがとても残念でした。
そこでDroidKaigi2017アプリをXamarinで作成してAndroid開発とXamarin Android開発を両方勉強してv( ̄Д ̄)v イエイしてみようと思い立って約3か月・・・・
ようやく一区切りできたので今回の記事を書いている状況です。
前提条件
今回の取り組みに関する知識・経験の前提を記載します。
- Xamarin Androidアプリは作った事があるけどFragmentとかViewとかサポートライブラリすら使ったことがなかった。
- Androidアプリは修正とかした事があるけど,FragmentとかViewとかサポートライブラリすら使っていないアプリだった。
- Xamarin.Formsを使用したAndroid/iOSのクロスプラットフォーム開発はチョットワカル
- Xamarin.Formsで利用している範囲のAndroidはフワッとわかる。
- Rxはちょっとわかるけど他ののプラットフォームのRxとかUniRxとかは使ったことない。
- Javaの言語仕様よく知らない。C#に似てるらしい。
- Kotlinの言語仕様はよく知らない。なんか新しいらしい。
- MVVMはくぁwせdrftgyふじこlp
今回目標にした事を記載します。
- DroidKaigi2017アプリのコードを読んでFragmentとかViewとか使い方と最近のAndroidアプリの作りを理解しよう。
- DroidKaigi2017アプリをXamarin Androidに移植してXamarin Android開発をしてみよう。
- モデル層を共通化してDroidKaigi2017 for iOSを作成してみよう(今回未達)
今回の成果
- GitHub - yuka1984/DroidKaigi2017forXamarin
- Xamarin Android および サポートライブラリのバグ発見 & 報告 (2件発見1件報告済み/1件が検証コード書けてないので未報告)
DroidKaigi2017アプリのコードを読んでみた
メモ取りながら作業をしていなかったので細かい点まで覚えていませんでした。
ですので印象的だったことをピックアップしていきたいと思います。
DI
Dagger2というライブラリを用いてDIを行っていた。
このDagger2というライブラリのパターンが私が普段よく使っている.NETのDIライブラリと使い方がかなり違っていて理解するのに一番時間がかかった。
Dagger2はアノテーションプロセッシングを活用してDIを実現してた。
クラスやメソッドにアノテーション(C#でいうAttribute)をつけることでインジェクションや依存関係を定義する。
私が普段書くプログラムではクラスにてDI設定を定義するはなくて、外部から(コード or Config)定義して使用する書き方をしていたし、それが普通だと思っていたのでビックリした。
どこでコンテナへのインジェクション設定を書いてるんだろうってずっと探してたけど、まさかアノテーション書くだけで実現していたとは・・・
Yukiの枝折: Android: Dagger2 - Subcomponent vs. dependencies
この辺を読んで勉強した。
DaggerはAndroidのライフサイクルに対応するための、特化したDIライブラリという感じだった。
でも結局Daggerの書き方には違和感があってなじまなかった。
私はクラスにDIに必要な要素を書きたくない(´・ω・`)
Rx
RxJava2とRxAndroidというライブラリが使われているみたい。
主に通信を非同期で扱うために使われていた。SingleというObservableの実装があってSingleをSubscribeして結果を受け取った後の処理を書く、みたいな感じだった。
C#でいうところのObservable.FromAsyncでTaskをObservableにしたものって感じ。Nextの次にCompleteが来るやつ。
この使い方ならasync/awaitで書けばよいのに?って思ったらJavaには無いっぽい。だからRxが発達してるんだなぁって理解できた。
通信系はRx使っておくとリトライとか書きやすいのでRxでも良いなぁって思った。
あとはDB操作系でもSingleな戻り値のパターンがあったかな。
retrofit2
これすごかった。
インターフェース書いてアノテーションでURL指定すると、それだけでHttpRequestの実装が終わってる( ゚Д゚)
.NETにも移植したものがあるらしい。
アーキテクチャ
いわゆるクリーンアーキテクチャのリポジトリーまでがあってUseCase以降はViewModelとなるパターン。
なんでUseCaseつくらないのかなぁって思ったけど、そもそも単一プラットフォームに対する実装であればUseCase部はViewModelにしちゃえばよいのかなぁって思った。
結構ViewModelでContextを使ってた。ViewModelのライフサイクルがFragmentのライフサイクルと一致していれば特に問題ないんだなぁって感じた。
でもちょっと違和感。それはFragmentとかViewでするべきでは?的な感じがした。
Android Data Binding
xmlのAttributeにViewModelとのバインディングを指定できるすごいやつ。Xamlバインディングとは仕組み的には結構違う。C#はその時にBindingするための処理をするけどAndroidではコンパイル前にバインディングクラスが作られてる。 感覚的にはxamlのコンパイルクラスにバインディングの為の関数類も生成されてますって感じ。
なので、単純にバインディングだけじゃなくてコードビハインド的なこともできる(FindViewByIdとか書かなくてよい。)
Converterとかない感じ? 見当たらなかった。そのせいなのかViewModelでViewのプロパティが生えている事が多かった。Visibilityとかリソースとか。
思ったこと
Activity Fragment Viewの使い方は大体わかった。
全体的にアノテーションプロセッシングとかJavaのツールを利用したライブラリが多くて、これをXamarinにそのまま移植するのはハードルが非常に高いと感じた。
Xamarin Androidは、確かにネイティブのAPIは殆どカバーしているけれどJavaの言語仕様や周辺ツール群をカバーしているわけじゃない。
VisualStudioでC#でXamarin Android開発を行う事は単純にJavaの代わりにC#を使うという事ではなくて全く別の開発プラットフォームであると認識したうえで.NETでよく使われるライブラリ群を利用してどのようにAndroid開発にフィットさせるかが大切なのではないか? というところまで考えて、移植作業ではその辺の部分を意識した開発を行ってみた。
DroidKaigi2017アプリをXamarinで再現してみた。
全ての機能を移植していません。セッション周りのみとなります。
他の画面はiOS移植を行う際にForms Embeddingの練習に使おうかと思います。
sleepyandhungry1984.tumblr.com動作はこんな感じです。横画面をキャプチャし忘れましたが問題ないです。
方針
どういう事を実現してみたかったかを書いてみます。
- Xamarin.Formsアプリでよく使っているパターンで実装してみたかった。
- できる限りAndroid産のインフラライブラリを使用しないで馴染みの深いライブラリを採用してXamarin Androidにフィットさせてみる。
- DroidKaigiアプリはconfigChangesを指定していたがArchitecture Componentsの考え方を使えばConfigChangesを指定せずに回転を乗り越えられそうだったので乗り越えてみた。
Architecture Components
コード書き始めた頃に発表になりました。
当然DroidKaigiアプリでは採用されていないんですが、気になったので調べてみて一部をC#に移植してみて適応してみました。
ちゃんとしたArchitecture ComponentsはXamarinチームの凄い人がXamarin環境でも利用できるようにしてくれると思います。
Nyanto
今回作成したインフラ周りはNyantoというプロジェクトにまとめました。
機会があったら独立させてパッケージ化しようかなぁと思いますが使っても私しか幸せになれないのでArchitecture Componentsを使ってください。
このフレームワークは
- AutofacとArchitectureComponentsのViewModel周りの実装を活用して良い感じにDIを行う
- ReactivePropertyの利用を行いやすくする。
ということを目的にしています。
ACのViewModelはHolderFragment
というRetainInstance = true
にしたFragment
にViewModelのインスタンスを持たせることによって、画面が回転してActivity/Fragmentのインスタンスが破棄されてもViewModelは維持される、というようなことをしています。
このコードを見た時に、真っ先に思ったことが、なんでViewModelの部分はGenericになっていないのだろう?っていううことでした。
なのでこのViewModel部分をGeneric化してC#に移植し、NyantoではAutofacのILifetimeScope
をあてはめて使用しています。
これにより SingletonはApplication
クラスのライフサイクルと同じで InstancePerLifetimeScope
はActivity
の存在期間と同じというスコープを実現しました。
またACのLiveDataにあたる部分はReadOnlySwitchReactiveProperty
というIReacOnlyReactiveProprty<T>
の実装を作成することで対応しました。
ReadOnlySwitchReactiveProperty
は作成時に通常のIObservable<T>
に加えてIObservable<bool>
を引数に取ります。
そしてbool IsActive
というプロパティを持ちます。
要するにIsActive
がtrue
の間はいつものReadOnlyReactiveProperty<T>
と同じように動作するけどfalseになった時には内部的にValue値は更新されるけどP```ropertyChangedは発生せず値が流れていかない、という動作をします。
またIsActiveがfalseからtrueに変化したときに最新の値を流します。
そしてViewModelBase
には
DroidKaigi2017forXamarin/ViewModelBase.cs at master · yuka1984/DroidKaigi2017forXamarin · GitHub
IObservable<bool>
のIsActiveObservable
というプロパティを持ちます。
IsActiveObservable
はNyantoの実装によってViewModelを使用するFragmentが乗っかるActivityのライフサイクルがstart or resumeの時にtrueを流しそれ以外に変化したときにはfalseを流します。
この二つを組み合わせることで画面を更新してはいけない時には更新しない、という仕組みを実現しています。
その他、NyantoにはIObservableとViewのプロパティのバインドを助けるためのExtentionsだったりが含まれています。
Nyanto.ViewSupportTool
FindViewByIdを書くのがつらかったので作りました。
AndroidのLayoutのxmlファイルからViewへのアクセスクラスを生成してみる - Qiita
少しは手間を減らせたと思います。
nugetパッケージ化してビルド前に走らせるとか出来るんですけど今回はそこまでしてません。めんどくさいです。
TwoWayView
セッション画面を実現するために使われています。RecyclerViewの実装なのですがGridなLayoutの拡張って感じです。
ここはすっごい苦労しました。 は 一応Bindingしてくれてるgituhubリポジトリがあったんですが、このライブラリはサポートツールのかなり古いバージョンを使用していてサポートライブラリバージョンの辻褄をどうしても合わせられなかったのでフルでC#移植を行いました。
その際にバージョン依存している部分は本家TwoWayViewのIssueに対応方法っぽいものが載っていた採用して移植をしています。最新のサポートライブラリで動作します。
こちらも機会があったらパッケージ化しようかなぁって思ったり思わなかったりしています。めんどくさい。
アーキテクチャ
私の好みのアーキテクチャで実装してます。
私の好きなやつです。以上です。
クリーンアーキテクチャに近い感じでリポジトリをSingleInstanceにしてObserverパターンで全体的に伝搬させるやつですね。
DIを活用して実装しているので結構気軽にインスタンスのライフサイクルの変更にも対応できます。
できる限りTwoWayにならないように設計することを心掛けるようにしています。
バックエンド
本家はGithubのjsonを読んだりgoogleフォームにフィードバックを送信したりしてるんですけどフィードバック送っちゃうのはだめだと思うのでバックエンドをAzure MobileAppsのEasyTableに置き換えました。オフライン同期とか使えるんですけど、ちょっと試してみてはまりそうだったのでオフライン同期は使用せずにそれっぽいことをしています。
開発としては最初から置き換えて実装したわけではなくて、最初はGithubからデータを取得するモッククラスやFeedbackの部分はオンメモリで登録したかのように動作するモッククラスを作成してView/ViewModelを作成していって、最後にAzureEasyTableを利用した実装で置き換える。
みたいな感じでいつもやっている開発手法がXamarin Androidでもちゃんとできて、当たり前ではあるけど良い経験ができました。
その他
本家ではViewModelでもContextを扱うような実装だったのですが、個人的にはあまり納得できなかったのでContextを扱う処理はFragmentの方で行うようにしました。
終わりに
細かい点を挙げれば、こうしたい、ああしたい、は沢山あるのですけど、ひとまずの目標を達成したので一区切りで次はiOSネイティブとXamarin iOSの学習に入ろうかと思います。