Mirrativ Androidエンジニアのmorizoooです。
MirrativのAndroidアプリでは、Jetpack Composeの1.0がリリースされた2021年7月から導入をはじめています。
今回の記事では、導入に至った経緯と、ミラティブで採用しているFluxアーキテクチャの中でJetpack Composeをどのように活用しているかについてお話します。
導入に至った背景
Jetpack Compose導入の背景としては公式ドキュメントにもあるように以下の点をメリットに感じて導入しました。
- コード量が削減できること
- ステートレスコンポーネントを簡単に作成できること
また、Jetpack Composeの開発に深く携わっているJim Sprochさんもツイートでも、旧Android Viewはメンテナンスモードと宣言されており、Jetpack Composeへの移行はほぼ不可避と判断しました。
Androidビューはメンテナンスモードです。 相互運用性レイヤーとして引き続きサポートしますが、すべての新しい開発とバグ修正はJetpackComposeに組み込まれます。 Composeは、Androidの未来の最新UIツールキットです。
— Jim Sproch (@JimSproch) May 19, 2021
Jetpack Composeで配信一覧画面を作る
Mirrativは、多くのユーザーがゲームや雑談などの配信をしています。 Jetpack Compose導入の具体例として、ユーザーの配信一覧を表示するサンプルを紹介します。
Mirrativで採用しているアーキテクチャ
MirrativではFluxアーキテクチャを選択しています。 Fluxのフローとしては下記の図の流れで行っています。
具体的な実装については、以前記事を書いたのでそちらをご覧ください。 tech.mirrativ.stream
Jetpack Composeを使用したサンプル
ActionCreator
fetchLives
で配信一覧情報を取得します。
class LiveActionCreator( private val dispatcher: Dispatcher, private val mirrativRequest: MirrativRequest, ) : CoroutineScope by IOScope() { fun fetchLives() { launch { try { val lives = mirrativRequest.getLives() dispatcher.dispatch(LiveActionEvent.FetchLivesSucceeded(lives)) } catch (error: MirrativError) { // 通常エラー処理を行っていますが、今回は割愛します } } } }
ActionEvent
ActionCreatorで取得した情報をStoreに流すための入れ物です
sealed class LiveActionEvent { data class FetchLivesSucceeded(val lives: List<Live>) : LiveActionEvent() }
Store
ActionEventを受け取り、Storeの持つPropertyに取得した情報を反映させます。
class LiveStore( private val dispatcher: Dispatcher, ) : ViewModel(), CoroutineScope by MainScope() { init { dispatcher.register(this) } override fun onCleared() { cancel() dispatcher.unregister(this) super.onCleared() } private val mutableLiveBindModels = mutableStateListOf<LiveBindModel>() val liveBindModels: List<LiveBindModel> = mutableLiveBindModels @Subscribe(threadMode = ThreadMode.MAIN) fun on(event: LiveActionEvent.FetchLivesSucceeded) { mutableLiveBindModels.addAll(event.lives.map { LiveBindModel.convert(it) }) } }
LiveBindModel
APIのResponseから、Viewで必要な状態にmappingしています。
class LiveBindModel( val liveId: String, val liveTitle: String, val userName: String, val profileImageUrl: String, ... ) { companion object { fun convert(live: Live) = LiveBindModel( liveId = live.id, liveTitle = live.liveTitle, userName = live.user.userName, profileImageUrl = live.user.imageUrl, ... ) } }
Jetpack Composeの実装
Page
JetpackComposeのRootのComponentを Page
としています。主に以下のことを行っています。
- LaunchedEffectで初期化処理。今回の場合は、初回表示にActionCreatorのfetchLivesを実行。
- 必要なViewにStoreの値とListenerを渡す
@Composable
fun LiveListingPage() {
val store = getViewModel<LiveStore>()
val actionCreator = get<LiveActionCreator>()
LaunchedEffect(Unit) {
actionCreator.fetchLives()
}
LiveListingView(
liveBindModels = store.liveBindModels,
onClickItem = { liveBindModel ->
actionCreator.onClickItem(liveId = liveBindModel.liveId)
} ,
)
}
必要なComponent
LazyColumnのitemsにliveBindModelsを設定します。
@Composable fun LiveListingView( liveBindModels: List<LiveBindModel>, onClickItem: (LiveBindModel) -> Unit, ) { LazyColumn { items(liveBindModels) { item -> LiveItemView(liveBindModel = item, onClickItem = onClickItem) } } }
余談ですが、MirrativでJetpack ComposeでViewを作る場合、ConstraintLayoutは使っていません。 小さくコンポーネントを作ると必要性を感じないのと、ConstraintLayoutを使う/使わない場合のルール決めが難しいという理由です。 公式でもConstraintLayoutを使うかどうかはデベロッパーの好みともあるのでMirrativでは利用していません。
まとめ
Fluxアーキテクチャの中でJetpack Composeを活用した事例について紹介しました。特に一覧画面など、元々はRecyclewViewを必要としていた箇所では、実装量が減り開発が早くなっていることを実感できています。 今月からは新規画面はJetpack Composeで作るという方針を決めており、積極的に利用していく予定です。
We are hiring!
ミラティブでは一緒にアプリを作ってくれる Android エンジニアを募集中です!気軽にご連絡ください! www.mirrativ.co.jp