Open28

[Android]compose1.5.4(BOM2023.10.01) enterキー(ハード)でボタンが押せない問題

saiki iijimasaiki iijima

composeのバージョンをあげたら今までenterで押せていたボタンが押せなくなったりしているので調査する。

saiki iijimasaiki iijima

起きてること
今までfocus+enterで発火していたButtonのonClickが呼ばれないケースがある。
(呼ばれるケースもある)

追記:
前提の情報を書き忘れていたんだけど、画面を開いた瞬間にボタンにfocusを当てたくてbuttonにfocusRequesterをセットしてrequestFocusしてます。
つまり、ButtonにFocusRequesterとfocasableをつけるとenterでonClickが呼ばれないケースがある。

saiki iijimasaiki iijima

androidStudioのnewProjectで作ったprojectがビルド失敗するの初心者に厳しくない?

saiki iijimasaiki iijima

まずfocusRequesterの挙動に違いあるんだっけ?の確認

ButtonにfocusableをつけずにrequestFocusした場合と、つけてした場合を見る。

focusRequesteの挙動としては
1.子の最初のfocasableにfocusする& Modifier.focusRequester()の下に書いたfocasableは子の最初のfocasable扱い(これは重要情報)

  1. なければ自分のfocasableにfocusする

だったはず。
TextはデフォルトでfocasableがないけどButtonはどうだったっけな。流石に今まではあった気がするけど…ということで実践。

a.focasableがないパターンのコード

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeButtonSampleTheme {
                var button1Count by remember { mutableIntStateOf(0) }
                val focusRequester = remember { FocusRequester() }
                LaunchedEffect(key1 = Unit) {
                    focusRequester.requestFocus()
                }

                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    Column(
                        modifier = Modifier.fillMaxSize(),
                        verticalArrangement = Arrangement.Center,
                        horizontalAlignment = Alignment.CenterHorizontally
                    ) {
                        Button(onClick = { /* no-op */ }) {
                            Text("Fake first Button")
                        }
                        Text(text = "ButtonCount : $button1Count")
                        Button(
                            modifier = Modifier
                                .focusRequester(focusRequester = focusRequester),
                            onClick = { button1Count = button1Count.inc() }
                        ) {
                            Text("Target Button")
                        }
                    }
                }
            }
        }
    }
}

b.あるパターンのコード

略...
                        Button(
                            modifier = Modifier
                                .focusRequester(focusRequester = focusRequester)
                                .focusable(),
                            onClick = { button1Count = button1Count.inc() }
                        ) {
                            Text("Target Button")
                        }

略...
saiki iijimasaiki iijima

compose 1.5.4
a.フォーカスされず。当然enterでonClickも呼ばれない。
b.フォーカスされる。enterでonClickも呼ばれる。

bならonClickも呼ばれるんだな…?

saiki iijimasaiki iijima

compose 1.4.3
a.フォーカスされず。当然enterでonClickも呼ばれない。
b.フォーカスされる。enterでonClickも呼ばれる。

1.5.4と同じだった。

saiki iijimasaiki iijima

あー
さっき書いた

なければ自分のfocasableにfocusする

は明示的にfocusRequesterの上にfocasableつけたらって話なのかな。
ButtonとかはclickableであってfocasableじゃないからfocusRequesterでfocusはしないけどclickableであればキーボードでのfocusは可能みたいな感じなのかな…

https://developer.android.com/jetpack/compose/touch-input/focus/change-focus-behavior#request-keyboard:~:text=For instance%2C code like the following makes the Box focusable%2C but the FocusRequester isn't associated with this focusable since it is declared after the focusable.

saiki iijimasaiki iijima

関係ないけど面白い(便利そうな)modifierのメソッド見つけた

saiki iijimasaiki iijima

もう一歩プロダクトコードに近づける。

Buttonを利用した独自のボタンを定義する

@Composable
fun OriginalButton(
    modifier: Modifier
) {
    var originalButtonCount by remember { mutableStateOf(0) }
    Button(
        modifier = modifier,
        onClick = { originalButtonCount = originalButtonCount.inc() }
    ) {
        Text("Original Button : $originalButtonCount")
    }
}

でさっきのボタンの代わりに以下を入れて試します。


                        OriginalButton(
                            modifier = Modifier
                                .focusRequester(firstFocusRequester)
                                .focusable()
                        )
saiki iijimasaiki iijima

compose 1.4.3
フォーカスされる。onClickも呼ばれる。

ok

1.5.4、頼むぞ…

saiki iijimasaiki iijima

compose 1.5.4
同上

ん〜?
何か別の条件がある?
チョンボだったらやだなあ

saiki iijimasaiki iijima

やっぱいプロダクトだとonClick呼ばれんな。
差分があって
画面開いた瞬間、isFocusedもhasFocusもtrueなのはどちらも一緒なんだけど、sampleの方はenterを押した瞬間にOriginalButtonのisFocusedがfalseになる(hasFocusはtrueのまま)
そして中のButtonのisFocusとhasFocusがtrueになる。

これはつまりenterが押された瞬間に子のclickableまでfocusを移動して実行してるってことだと思うんだけど、なんでサンプルはそうなっていてプロダクトはそうならないの?なんでなんで?

saiki iijimasaiki iijima

プロダクトの方は依存の関係でcomposeのバージョン下げるのが簡単にできないのがまた難しいな

saiki iijimasaiki iijima

もしかしてcomposeBomではなく何か他のバージョンかもしれないぞ

saiki iijimasaiki iijima

composeと合わせてあげられたなにがしのバージョンと実装のハーモニー説が濃厚になってまいりました。
疑ってごめんねcompose…

saiki iijimasaiki iijima

composeDestinationsを使っているんですが
"1.8.42-beta"-> "1.9.54"
へのバージョンアップ+それに伴う何かしらの変更で発生したというところまでは確定しました。
composeDestinationsの問題なのかこちらの実装の問題なのかはこれから調査します。

本当に疑ってごめんなcompose…

saiki iijimasaiki iijima

やっぱcomposeによる挙動の変更が関係してました。
でもこちらの実装とのハーモニーだった。

saiki iijimasaiki iijima

前提の情報を書き忘れていたんだけど、画面を開いた瞬間にボタンにfocusを当てたくてbuttonにfocusRequesterをセットしてrequestFocusしてます。