Part 1: ViewState のモデリング - 非同期なデータ取得

はじめに 今日のAndroid アプリ開発においては, ViewState を作るのが主流になってきたかと思います. 本シリーズでは, ViewState をモデリングするにあたって必要な考え方や実際の方法, そしてアプリ要件に合わせた応用的な実装について紹介していきます: 非同期なデータの取得状態の表現 統一的な Loading, Error 状態の表現 イベントの表現 今回は第1回として, もっともベーシックな 非同期なデータの取得状態の表現 について解説していきます. 同様の記事は既にたくさんあるので合わせて参照することをおすすめします. なお, 本シリーズが続く保証はありません… 😿 説明に当たっては, 次のような構成の To-do 管理アプリをベースに解説していきます. TasksFragment -> TasksViewModel (<-> TaskRepository <-> ...) <- TasksViewState -data class Task( val id: String, val title: String, val completedAt: String, )class TasksViewModel : ViewModel() { private val _state = MutableLiveData<TasksViewState>(TasksViewState()) val state: LiveData<TasksViewState> = _state } 非同期なデータの取得状態の表現 先に結論を述べると, 他の記事にもある通り, 次のようなクラスで表現するのが今のところ無難だろうということです....

Serviceが起動しているかどうかをチェックする

背景 自作の Service が起動中かどうかをチェックしたかったので調べたのでメモ. 検索してみると, 以下の StackOverflow に最終的に行き着いた. How to check if a service is running on Android? - stackoverflow しかし, 上記の方法だと, ActivityManager#getRunningService(Int) が Android 8.0 から Deprecated になっているため, 現在は使うことができない. 他の手段を調べた結果, LocalBroadcastManagerを使えば出来そうだということが分かったので, これを使って実装してみた. LocalBroadcastManager 自身のプロセス内にのみBroadcastされる Intent を発行できるやつです. 受信側が自身のプロセス内であることが保証されるので安全です. (Deeplink を自身のプロセスで起動した場合はその限りではないかも) EventBus なんかと似たような動作をします. 実装 LocalBroadcastManager#sendBroadcast は送信に成功したかどうか(受信側が存在していたるかどうか)を返り値で取得できる. つまり, Service が起動している間だけ受信側を用意すれば, その返り値によって Service の起動状態を確認することができる. 具体的には, Service 側で以下のような実装をする. class HelloService : Service() { companion object { const val ACTION_IS_RUNNING = "com.example.HelloService_is_running" fun createIntent(context: Context) = Intent(context, HelloService::class....

LiveDataをモックした時にClassCastExceptionが発生する問題の解決

環境 Kotlin: 1.3.21 lifecycle-extensions: 2.1.0-alpha03 mockk: 1.9 テスト時にはDaggerを経由してViewModelFactoryおよびViewModelが生成され, テスト時もDaggerを用いてViewModelを生成しています. 詳しい方法についてはこちらのQiita記事を参照してください. 問題 ViewModel を使った Activity/Fragment のテストをするとき, 次のような, モデルクラスのListを返すようなLiveDataを持ったViewModelを定義して使っているものとします. このとき, ArticleViewModel$articles を Activity/Fragment から上記のような形で呼び出した時に ClassCastException が発生してしまいます. ArticleViewModel.kt open class ArticleViewModel() : ViewModel() { private val _articles by lazy { MutableLiveData<List<Article>>() } val articles: LiveData<List<Article>> get() = _articles // ... } `ArticlesFragment.kt val articles = viewModel.articles.value エラー java.lang.ClassCastException: java.lang.Object cannot be cast to java.util.Collection 解決策 通常は次のような記述でViewModelを記述しますが, 今回は Activity/Fragment のテストをしたいのでViewModelをモック化します. @Module interface ViewModelModule { @Binds @IntoMap @ViewModelKey(ArticleViewModel::class) fun articleViewModel(viewModel: ArticleViewModel): ViewModel } モック化した ArticleViewModel を提供するには, 上記の記述の代わりに次のような記述をします....