LoginSignup
5
0

ViewModel に依存しない Composable を書こう

Last updated at Posted at 2023-11-30

はじめに

Android Advent Calendar 2023 1日目の記事です。

ViewModel に依存しない Composable

Composable が ViewModel に依存すると Preview を書くのが難しくなったり、 ViewModel を Composable から触れすぎてしまうのでコードが複雑になりやすいので避けたいです。

Android Develpers のガイドで示されているサンプルからコードを抜粋して紹介します。

Good

次のような ViewModel に依存していない Composable を書きたいです。

@Composable
fun HomeContent(
    featuredPodcasts: PersistentList<PodcastWithExtraInfo>,
    isRefreshing: Boolean,
    selectedHomeCategory: HomeCategory,
    homeCategories: List<HomeCategory>,
    modifier: Modifier = Modifier,
    onPodcastUnfollowed: (String) -> Unit,
    onCategorySelected: (HomeCategory) -> Unit,
    navigateToPlayer: (String) -> Unit
) {

HomeContent の呼び出し側で ViewModel や ViewState の依存を注入しています。

        HomeContent(
            featuredPodcasts = viewState.featuredPodcasts,
            isRefreshing = viewState.refreshing,
            homeCategories = viewState.homeCategories,
            selectedHomeCategory = viewState.selectedHomeCategory,
            onCategorySelected = viewModel::onHomeCategorySelected,
            onPodcastUnfollowed = viewModel::onPodcastUnfollowed,
            navigateToPlayer = navigateToPlayer,
            modifier = Modifier.fillMaxSize()
        )

Bad

ViewModel に依存してしまう Composable として次のように書くこともできてしまいます。

@Composable
fun HomeContent(
    featuredPodcasts: PersistentList<PodcastWithExtraInfo>,
    isRefreshing: Boolean,    
    homeCategories: List<HomeCategory>,
    viewModel: HomeViewModel,
    modifier: Modifier = Modifier,
    navigateToPlayer: (String) -> Unit
) {

ViewModel を利用すると次のように書くこともできてしまいます。

-               onClick = { onCategorySelected(category) },
+               onClick = { viewModel.onHomeCategorySelected(category) },

こうすると、 HomeContentHomeCategoryTabsHomeViewModel に依存するので、 Preview を書くのが難しくなります。

Handle user event のコード例を見ると避けたいと書いた onClick = { viewModel.refresNews() } としています。このコード例の影響もあって、 Composable に ViewModel を渡すことの違和感を消しているのかもしれません。

@Composable
fun LatestNewsScreen(viewModel: LatestNewsViewModel = viewModel()) {

    // State of whether more details should be shown
    var expanded by remember { mutableStateOf(false) }

    Column {
        Text("Some text")
        if (expanded) {
            Text("More details")
        }

        Button(
          // The expand details event is processed by the UI that
          // modifies this composable's internal state.
          onClick = { expanded = !expanded }
        ) {
          val expandText = if (expanded) "Collapse" else "Expand"
          Text("$expandText details")
        }

        // The refresh event is processed by the ViewModel that is in charge
        // of the UI's business logic.
        Button(onClick = { viewModel.refreshNews() }) {
            Text("Refresh data")
        }
    }
}

関連

Twitter Jetpack Compose Rulesでは以下のルールを定めています。

  • ViewModelを子の関数に渡さない。

おわりに

2日目は「恥ずかしがり屋の ViewModel」というタイトルでより良い ViewModel について続きを書こうと思います。

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0