Sansan Tech Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

ComposableのonClickはラムダ式?Listener?

こんにちは!Sansan技術本部Mobile Applicationグループのふるしんです。

以前の記事で「アーキテクチャ検討会」を実施しているお話を書きました。
buildersbox.corp-sansan.com

この検討会の中ではどのような議論がなされているのかを聞かれる機会があり、せっかくなのでご紹介します。

実際にあった議論


Jetpack Composeで実装する際、ボタンのonClickイベントに対する処理を渡すことになります。
よくやる方法としては、上位のComposableからラムダ式を順に引数で渡してあげる方法です。
これってラムダ式を渡すのがやりやすいのか、Listenerを渡すのがやりやすいのか。
またListenerにする場合に画面とダイアログでListenerをまとめるか分割するか、どのような方法が私たちにとってわかりやすいのかを議論しました。

Android公式ドキュメントではラムダ式を渡す手法で書かれているものが多いです。

@Composable
fun Screen(onButton1Click: () -> Unit,
           onButton2Click: () -> Unit,
           onButton3Click: () -> Unit,
           onButton4Click: () -> Unit,
           onButton5Click: () -> Unit){
    Surface(
        modifier = Modifier.fillMaxSize(),
        color = MaterialTheme.colorScheme.background
    ) {
        Column {
            Button(onClick = onButton1Click) { Text(text = "Button1") }
            Button(onClick = onButton2Click) { Text(text = "Button2") }
            Button(onClick = onButton3Click) { Text(text = "Button3") }
            Button(onClick = onButton4Click) { Text(text = "Button4") }
            Button(onClick = onButton5Click) { Text(text = "Button5") }
        }
    }
}

他にもListenerを渡してあげる方法があります。

interface Listener {
    fun onButton1Click()
    fun onButton2Click()
    fun onButton3Click()
    fun onButton4Click()
    fun onButton5Click()
}

@Composable
fun Screen(listener: Listener) {
    Surface(
        modifier = Modifier.fillMaxSize(),
        color = MaterialTheme.colorScheme.background
    ) {
        Column {
            Button(onClick = { listener.onButton1Click() }) { Text(text = "Button1") }
            Button(onClick = { listener.onButton2Click() }) { Text(text = "Button2") }
            Button(onClick = { listener.onButton3Click() }) { Text(text = "Button3") }
            Button(onClick = { listener.onButton4Click() }) { Text(text = "Button4") }
            Button(onClick = { listener.onButton5Click() }) { Text(text = "Button5") }
        }
    }
}

どちらのコードも「ボタンをタップされた時に処理をする」ことは達成できます。ではどちらの手法がよりBetterなのか?を議論しました。
まずはそれぞれのメリット・デメリットを見てみます。

ラムダ式の場合

メリット

  • どのイベント(ボタンクリックなど)に対してどの処理(ラムダ式)が必要なのかがわかりやすい
  • Listenerを用意してあげる必要がない

デメリット

  • ボタンなどイベントが起こるものが多くなると上位のComposable関数から渡すラムダ式が大量になってしまい、可視性を大きく損なう場合がある

(実際他チームで数十個のラムダ式が並んでしまった)

    • 引数として渡すのを忘れてしまう可能性がある

Listenerの場合

メリット

  • イベントの意味ごとにまとめられる
  • Listenerを渡すだけでよく、ボタンなどが多くなったとしても可視性は損なわれにくい

デメリット

  • 小さな画面でも毎回Listenerを用意する必要がある(手間がかかる)

結論

Listenerを渡す
我々としては、やはりラムダ式におけるデメリットの「数十個のラムダ式が並んでしまって可視性を損ねてしまう」が大きな理由として選択肢としては選びづらく、Listenerを渡す判断をしました。
結果として「このListenerを見ればこの画面でどんなイベントが起こるのかがだいたいわかる」ということにもなり、良いまとめ方だったのではないかと思っています。

また、Lisntenerを画面とダイアログでそれぞれで作るという結論にもなりました。
画面側にダイアログのイベントに反応するInterfaceを持たせていると、もしダイアログが不要になった場合にListenerのInterfaceからもonHogeClickのようなものの削除を忘れてしまう可能性があります。
その点、例えばHogeDialogFragmentにInterfaceを定義しておけば、ダイアログの実装が削除されればInterfaceも忘れずに一緒に削除できるメリットがあります。

このように、何か達成したいことがあって複数の手法がある場合にはチームで話し合い、最終的にはコードに落とし込むところまでを頻繁に実施しています。

技術的な議論に参加したい方や技術的な議論に飢えている方、お話しませんか?
open.talentio.com

© Sansan, Inc.