• トップ
  • ブログ一覧
  • JetpackCompose日記(最近よく使っている構成)
  • JetpackCompose日記(最近よく使っている構成)

    はじめに

    しばらくJetpackComposeの実装経験を積んできたことで。
    ある程度構成が固まってきたので備忘録程度の記事にします。
    とはいえ、常に流行りは変化しているので、私の構成も数カ月後には変わっているかも。

    最近よく使っている構成

    • 1画面1Activity(画面遷移はActivityの遷移で実現)
    • Fragmentは使わない
    • DIはHiltを使う

    と、まぁ・・・まだActivityによる遷移を捨てきれていないエンジニアです。
    AndroidViewとComposeが共存する過渡期なので、このくらいのCompose化が一番無難かな~とは思っています。

    Activity

    Activityには、最低限の処理のみ記載します。
    主にComposeから受け取った画面遷移のイベントを受けて、Activity遷移を行う役割に限定されると思います。
    将来的にJetpackComposeへの完全移行を見据え、Activityへの依存を極力抑えています。

    1@AndroidEntryPoint
    2class MainActivity : ComponentActivity() {
    3
    4    override fun onCreate(savedInstanceState: Bundle?) {
    5        super.onCreate(savedInstanceState)
    6        setContent {
    7            HogeTheme {
    8                MainScreen(
    9                    navigateToNextScreen = ::navigateToNextActivity
    10                )
    11            }
    12        }
    13    }
    14
    15    private fun navigateToNextActivity() {
    16        // TODO: startActivity
    17    }
    18}

    ViewModel

    特殊なことはしていないですが、意識していることはいくつかあります。

    • ユーザの操作の名前でイベントのメソッドを設置(onChangeText、onClickSubmitなど)
    • データの操作を意識させるような名前にはしない(onUpdateTextみたいな名前はNG)
    1@HiltViewModel
    2class MainViewModel : ViewModel() {
    3
    4    // UiState
    5    private val _uiState = MutableStateFlow(MainUiState.Loading)
    6    val uiState = _uiState.asStateFlow()
    7    private val mainContentUiState: MainContentUiState?
    8        get() = (uiState.value as? MainUiState.Succeeded)?.mainContentUiState
    9
    10    // 遷移イベント
    11    private val _navigateEvent = MutableSharedFlow()
    12    val navigateEvent = _navigateEvent.asSharedFlow()
    13
    14    fun onChangeText(value: TextFieldValue) {
    15        val mainContentUiState = mainContentUiState ?: return
    16        _uiState.update {
    17            MainUiState.Succeeded(
    18                mainContentUiState.copy(textFieldValue = value)
    19            )
    20        }
    21    }
    22
    23    fun onClickSubmit() {
    24        viewModelScope.launch {
    25            _navigateEvent.emit(Unit)
    26        }
    27    }
    28}

    ScreenCompose

    • 1画面分のCompose
    • 初期表示のLoadingなどの特殊な状態はここに記載
    • メインの表示物はComtentComposeに記載
    • プレビューは用意しない(後述のContentComposeで)
    • ViewModelはhiltでインジェクト(hiltViewModelはhilt-navigation-composeにあるのでプロジェクトに追加してください)
    1@Composable
    2fun MainScreen(
    3    navigateToNextScreen: () -> Unit,
    4    viewModel: MainViewModel = hiltViewModel()
    5) {
    6    val uiState: MainUiState by viewModel.uiState.collectAsStateWithLifecycle()
    7    
    8    // 遷移イベント(collectWithLifecycleについては後述)
    9    viewModel.navigateEvent.

    ContentCompose

    普通のComposable関数です。
    UiStateは基本的に対応するComposesable関数と同じファイルで定義しておくと再利用も編集もしやすいです。
    プレビューはPreviewParameterProviderを活用。

    1@Composable
    2fun MainContent(
    3    uiState: MainContentUiState,
    4    onChangeText: (TextFieldValue) -> Unit,
    5    onClickSubmit: () -> Unit
    6) {
    7    // TODO: uiStateやonChangeTextを用いてViewを定義する
    8}
    9
    10data class MainContentUiState(
    11    val textFieldValue: TextFieldValue
    12)
    13
    14private class PreviewParam : PreviewParameterProvider {
    15    override val values: Sequence = sequenceOf(
    16        MainContentUiState(
    17            textFieldValue = TextFieldValue("hoge")
    18        ),
    19        MainContentUiState(
    20            textFieldValue = TextFieldValue("fuga")
    21        )
    22    )
    23}
    24
    25@Composable
    26@Preview(showBackground = true)
    27fun MainContentPreview(
    28    @PreviewParameter(PreviewParam::class) uiState: MainContentUiState
    29) {
    30    HogeTheme {
    31        MainContent(
    32            uiState = uiState,
    33            onChangeText = {},
    34            onClickSubmit = {}
    35        )
    36    }
    37}

    collectWithLifecycleについて

    SharedFlowをActivityのlifecycleで監視するためのCompose用の拡張関数。
    collectAsStateWithLifecycle()でstateに変換してしまうと、同じイベントが連続で来たときに対応できなくなるのでSharedFlowのままでCollectする。
    (まあ、OneショットイベントをComposeで監視するべきかどうか?という論点は残っているとは思う。)

    1@Composable
    2@SuppressLint("ComposableNaming")
    3inline fun  SharedFlow.collectWithLifecycle(
    4    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
    5    minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
    6    collector: FlowCollector
    7) {
    8    LaunchedEffect(Unit) {
    9        lifecycleOwner.lifecycle.repeatOnLifecycle(minActiveState) {
    10            this@collectWithLifecycle.collect(collector)
    11        }
    12    }
    13}

    おわり

    以上!JetpackComposeもっと勉強していきたい。

    ライトコードでは、エンジニアを積極採用中!

    ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。

    採用情報へ

    おすすめ記事

    エンジニア大募集中!

    ライトコードでは、エンジニアを積極採用中です。

    特に、WEBエンジニアとモバイルエンジニアは是非ご応募お待ちしております!

    また、フリーランスエンジニア様も大募集中です。

    background