💥

【Flutter】Gesture付きTextSpanはMergeSemanticsで囲むとクラッシュ

2021/09/09に公開
開発環境
Flutter 2.5.0 • channel unknown • unknown source
Framework • revision 4cc385b4b8 (30 hours ago) • 2021-09-07 23:01:49 -0700
Engine • revision f0826da7ef
Tools • Dart 2.14.0

利用規約プライバシーポリシーのテキストリンク付きの同意テキストはよくある形だと思う。
これにチェックボックスが付くような場合もあるだろう。

結論

Flutterのバグなので、直るまで待つ!

ちなみに2.2.2でも発生していた。

問題

テキストリンクを含んだを実装する時、TextSpanを使うのが一般的かと思う。
ただこのまま実装すると、iOSでいうVoiceOverやAndroidでいうTTSで読み上げた場合、テキストとリンク付きのテキストで区切られて読み上げられてしまい、あまりいいアクセシビリティ対応とは言えない。
そこで、MergeSemanticsで囲って1つの塊として読み上げさせようとすると、exceptionが発生する。

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

class CrashSemanticsPage extends StatelessWidget {
  const CrashSemanticsPage({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Crash Semantics Page'),
      ),
      body: Center(
        child: MergeSemantics(
          child: Text.rich(
            TextSpan(
              children: [
                  Check
                TextSpan(
                  text: '利用規約',
                  recognizer: TapGestureRecognizer()
                    ..onTap = () {
                      //TODO: WebView表示
                    },
                ),
                const TextSpan(text: 'に同意する。'),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

発生条件

発生条件としては、recognizerを持つTextSpanMergeSemanticで囲った時となっている。

詳しくは以下のFlutterのissue
https://github.com/flutter/flutter/issues/41134

回避策?

案1 読み上げを妥協

素直にTextSpanでテキストリンクを含んだ文章を構築する。

メリット:

  • 自然な作り
  • バグが直るのをまつだけで変更不要

デメリット:

  • 読み上げがテキストリンク前後で分断されアクセシビリティとしてはよくな状態になる

案2 テキストリンクを別のに置き換える

テキストリンクにあたる部分をTextButtonInkWell+Textを使って見た目上それぽいものを構築する。

メリット:

  • 見た目は案1と遜色ない

デメリット

  • 読み上げが倒置法みたいに「に同意する 利用規約 ボタン」となり問題解決になっていない
  • ボタンタップ時のripple effectを消したりするカスタマイズが必要
  • アクセシビリティでフォントサイズを大きくした時、ボタンとテキストで折り返し位置が異なりぐちゃる

その他

テキストリンクにあたる部分はジェスチャーを入れず、手前に透明なボタンをStackで重ねて置くことを考えたが、現実的じゃないので案に入れるのを却下

Discussion