概要
転職活動の一環としてかなり昔(AndroidStudioがなかった頃)に触っていたAndroidアプリについてもう一度勉強し直していた。
その出力として、自分が感じていたWindowsアプリとAndroidアプリの差分をまとめておく。
自分と同じような経歴の人がAndroidアプリを作るときに助けになれば幸い。
開発環境
比較対象はこのような感じになる
Windows
- 利用言語
- C#
- プラットフォーム
- .Netもしくは.Net Framework
- UI
- WPF
Android
- 利用言語
- Kotlin
- API
- 31
共通点
とりあえずざっくりとだが、変化点を確認する前に共通点を上げておく
- どちらもMVVMパターンが推奨されている
- C#自体がJavaを意識した言語であるため、言語自体に共通点が多い
- 個人的にはコレクション操作がLINQと同じように使えて便利だった
また、プロジェクトの構成としてどちらもDIモジュールを使うとする。
変化点
APIの安定性
下手すると20年前のコードがそのまま動くぐらいの.Net系と比較してしまうとAndroidのAPIは
不安定さを感じてしまう。
Android Studioのテンプレートは結構な確率で陳腐化しているし、個人サイトの情報も数年前の情報であるとより良い方法があることも多い。
最初から公式のWebサイトから情報を得るのが無難そうだった。
※ただしその内容が使えるかどうかは結局自身で判断する必要がある。(後述)
ライフサイクル
手っ取り早く言うと、C#のGUIでは基本的にユーザがウィンドウを閉じる操作などを行ったときにGUIの破棄が起こるが、Androidはシステムが積極的に使わないUIをより積極的に廃棄する。
文法レベルでイベントがサポートされているC#プログラマとしては、これにより事実上イベント駆動ができなくなっている点が大きな差異だと感じた。
例えばMVVMで、フラグメントがViewModelからイベントを受け取ろうとしたらこんな感じになるだろうか。
(必要なライブラリに関しては省略。)
class SampleFragment: Fragment{
private val viewModel: SampleViewModel by viewModels()
override fun onResume(){
viewModel.setOnEventTriggeredListene(eventListener)
}
private val eventListener = SampleEventListener{
// 実際の処理がこの辺に入る
}
}
class SampleEventListener(val onEventTriggered: () -> Unit)
class SampleViewModel: ViewModel{
private val listener: SampleEventListener?
fun setOnEventTriggeredListene(eventListener: SampleEventListener){
listener = eventListener
}
}
(雑な実装だからというのもあるが)このような実装だと様々な問題が出てきてしまう。
- 画面回転などでFragmentを再生成するため、ViewModelが古いフラグメントの参照を持ち続ける
- 単純にリークしてるし、その後のイベントが動作しない、もしくはクラッシュの原因になる
- 単純にイベントハンドラ、購読、解除の実装を全イベントに対して行わなければいけないため、可読性がガタ落ちする
- 特にイベントハンドラはイベントの数だけ作る事になり、大量のほぼ同じ内容のクラスが生成されることになってしまう
- 別にViewModelの話だけではなく、どこにイベントリスナを置いてもライフサイクルの話がついて回るため、頭のリソースを大量消費する
このような問題から、AndroidのAPIで提供されているもの以外はLiveDataなどの他の方法を先に考える必要がある。
UIテスト
結論から手早く言うと、まともな方法では非同期の処理が含まれているUIのテストはできない。
Googleが提示している方法がこれだが…
要は「Thread.sleepを使いたくなかったら本番コード内にUIテストを行うときの実装を入れてね」と言っている。
株式会社ゆめみさんのコードチェック中に上記アドレスの通りに実装したが、
しっかりと「本番コード内にテストコードが紛れている」判定をもらっているため、
やっぱりおかしな実装だったらしい。
上記のアドレスのどの実装方法を使ってもダメ出しが入る未来しか見えないため、
まともな非同期のUIテストはできない、という結論になった。
(ちなみにゆめみさんのコードチェック自体は上記のイベント絡みの部分で可読性がガタ落ちさせてしまったり、
ここのテストコードでも評価を落としたこともあり、不合格となった。)
DIコンテナ
これは違いというより、個人的に戸惑った部分。
KotlinのDIコンテナ(Hilt)ではアノテーションでクラス間の関係の情報を用意して、コードを自動生成して対応するようだ。
アプリケーションのエントリポイントがわかりにくいからというのは理解できるが、
ぱっと見では到達不能コードを作り込んでるように見えてしまう。
TDDなどのリファクタリングを比較的多く行う開発手法では自動的にチェックさせる方法が必要になりそうだと感じた。
ちなみにC#の方はMicrosoft.Extensions.DependencyInjectionを使う場合、
下記のように依存性の登録を行うようなコードを何処かに書く必要がある。
まとめ
AndroidのAPIやライブラリはライフサイクルの存在により独特な進歩を遂げており、
主にWindowsのアプリを今まで作ってきた自分にとっては、それが大きな参入障壁になっているという印象を受けた。
メモリやCPUが進歩していることもあり、もうそろそろ力技で解決ができないのかな、とは少し思っている。