JetpackCompose日記(最近よく使っている構成)
IT技術
はじめに
しばらく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もっと勉強していきたい。
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ