はじめに
ブランドソリューション開発本部フロントエンド部FAANSの山田(@yshogo87)です。
本投稿では、すでにXMLで書かれたレイアウトをJetpack Composeにリファクタリングした理由とその手順について紹介します。
リファクタリングする画面の問題点
FAANSでは「コーデ閲覧数、送客数、売上数」を表示する画面があります。
この画面はUIの状態が一元管理されておらず状態がViewのみにしかないことで、機能の追加修正時に不具合を作り込みやすく、リリースまでに時間がかかるという問題がありました。
UIの状態変更が一元管理されていない
この画面ではUIの状態変更が複数の異なるソースコードから行われていて、一元管理されていませんでした。UIの状態を変えている場所は大きく分けると2つです。
現在のUIの状態変更が別のUIから行われる
この画面は1つのXMLファイルにViewPagerとRecyclerViewがあり、ViewPagerのグラフタップや横スワイプで下のRecyclerViewの情報も変わる仕様になっています。
この仕様のため、AdapterクラスにFragmentのBindingを渡して直接別のRecyclerViewの状態を変更する実装でした。
// Adapterクラス
class CoordinateImpressionsGraphAdapter(
private val binding: FragmentCoordinateImpressionsBinding,
private val viewModel: CoordinateImpressionsActionDelegateImpl,
diffCallBack: DiffCallBack = DiffCallBack()
) : RecyclerView.Adapter<CoordinateImpressionsGraphBindingHolder>() {
override fun onBindViewHolder(holder: CoordinateImpressionsGraphBindingHolder, position: Int) {
・
・
・
// グラフのタップイベント
chart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener {
override fun onValueSelected(e: Entry, h: Highlight?) {
// 別のRecyclerViewを直接更新している
(binding.impressionsDetail.adapter as CoordinateImpressionDetailAdapter).submitList(initialBrandList)
}
override fun onNothingSelected() {}
})
・
・
・
}
}
このようにAdapterクラスから別のUIの状態を変更され、現在のUIの状態がViewにしか保持されていないことが問題でした。UIの状態がViewだけに存在すると、状態の把握・管理することが難しくなり実装に時間を要していました。
UIの状態変更が複数のLiveDataから行われる
ViewModelからFragmentにイベントやUIの状態変更時にLiveDataを使っていました。これらのLiveDataは「UIの状態変更すること」と「イベントをFragmentに伝えること」の2つの役割があって区別されていませんでした。
class CoordinateImpressionsViewModel @Inject constructor(
private val coordinateImpressionsUseCase: CoordinateImpressionsUseCase
) : ViewModel() {
・
・
・
・
private val _navigatePopStack = MutableLiveData<Event<Unit>>()
val navigatePopStack: LiveData<Event<Unit>>
get() = _navigatePopStack
private val _coordinateImpressions = MutableLiveData<WearCoordinateImpressions>()
val coordinateImpressions: LiveData<WearCoordinateImpressions>
get() = _coordinateImpressions
private val _hasError = MutableLiveData<ErrorType>()
val hasError: LiveData<ErrorType>
get() = _hasError
private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean>
get() = _isLoading
private val _currentPosition = MutableLiveData<Int>()
val currentPosition: LiveData<Int>
get() = _currentPosition
UIの状態を変更するLiveDataが分かれているので、状態を変更する処理を追って確認しながら実装していく必要があるため、機能追加や仕様変更に時間を要していました。
UIの状態を一元管理するようにする
「コーデ閲覧数、送客数、売上数」を表示する画面はFAANSアプリのメイン機能であり、今後も機能追加されることが予想されることから以前より、リファクタリングを検討していました。PMチームなどにも現状の問題点を共有して、タスクを調整し時間をとってリファクタリングすることとなりました。
「現在のUIの状態変更が別のUIから行われること」と「複数のLiveDataからUIの状態が変更されること」の2点の問題を解決するためにUIの状態変更を一元管理するように修正します。
LiveDataを1つにし、Jetpack Composeに変更するリファクタリングを行う
UIの状態変更を一元管理するために、データの流れを整理するようにします。また、「UIの状態変更が複数のLiveDataから行われる」という問題を解決するために、LiveDataは1つにするようにします。
そしてFAANS Androidアプリで以前よりJetpack Composeを導入しているため、UIもXMLファイルからJetpack Composeに変更していきます。
UIの状態を一元管理する
リファクタリング方針の紹介
「UIの状態変更を一元管理」するために「単方向データフロー」の設計パターンを取り入れることにしました。データの流れを単方向にすることで明確にし、状態の把握・管理をしやすくしていきます。
UIの状態管理する2つのクラスを作成します。
続きはこちら