Mirrativ Tech Blog

株式会社ミラティブの開発者(バックエンド,iOS,Android,Unity,機械学習,インフラ, etc.)によるブログです

【Android】Jetpack Composeの活用法について

Mirrativ Androidエンジニアのmorizoooです。

MirrativのAndroidアプリでは、Jetpack Composeの1.0がリリースされた2021年7月から導入をはじめています。

今回の記事では、導入に至った経緯と、ミラティブで採用しているFluxアーキテクチャの中でJetpack Composeをどのように活用しているかについてお話します。

導入に至った背景

Jetpack Compose導入の背景としては公式ドキュメントにもあるように以下の点をメリットに感じて導入しました。

  • コード量が削減できること
  • ステートレスコンポーネントを簡単に作成できること

developer.android.com

また、Jetpack Composeの開発に深く携わっているJim Sprochさんもツイートでも、旧Android Viewはメンテナンスモードと宣言されており、Jetpack Composeへの移行はほぼ不可避と判断しました。

Jetpack Composeで配信一覧画面を作る

Mirrativは、多くのユーザーがゲームや雑談などの配信をしています。 Jetpack Compose導入の具体例として、ユーザーの配信一覧を表示するサンプルを紹介します。

ライブ一覧画面

Mirrativで採用しているアーキテクチャ

MirrativではFluxアーキテクチャを選択しています。 Fluxのフローとしては下記の図の流れで行っています。

flow

具体的な実装については、以前記事を書いたのでそちらをご覧ください。 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