方言を話すおしゃべり猫型ロボット『ミーア』をリリースしました(こちらをクリック)

【Flutter】RenderFlex overflowedエラーの対応法。Flutter DevToolsのInspectorを使用して問題を特定。

この記事は約11分で読めます。

はじめに

現在、方言を話すおしゃべり猫型ロボット「ミーア」を開発中。

ミーア
おしゃべり猫型ロボット「ミーア」は、100以上の種類の豊かな表情で、悲しみや喜びを共有します。様々な性格(皮肉・おせっかい・ロマンチスト・天然)や方言(大阪弁・博多弁・鹿児島弁・広島弁)も話してくれます。ミーアとの暮らしで、毎日の生活をもっ...

Flutterで、下記のようなRenderFlex overflowedエラー」が生じた時の、デバッグ方法と対応法を記載。

RenderFlex overflowedエラーとは何か?

端的にいうと「ウィジェットのサイズが画面範囲を超えたときに起こるエラー」のこと。

Flutterのドキュメントに、最も頻繁に発生するエラーの 1 つとして記載されている。

https://docs.flutter.dev/testing/common-errors

これが発生すると、黄色と黒の縞模様が表示され、アプリ UI のオーバーフロー領域を示す。さらに、デバッグ コンソールにエラー メッセージが表示される。

Dart
══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════

The overflowing RenderFlex has an orientation of Axis.vertical.
The edge of the RenderFlex that is overflowing has been marked in the rendering with a yellow and
black striped pattern. This is usually caused by the contents being too big for the RenderFlex.
Consider applying a flex factor (e.g. using an Expanded widget) to force the children of the
RenderFlex to fit within the available space instead of being sized to their natural size.
This is considered an error condition because it indicates that there is content that cannot be
seen. If the content is legitimately bigger than the available space, consider clipping it with a
ClipRect widget before putting it in the flex, or using a scrollable container rather than a Flex,
like a ListView.
The specific RenderFlex in question is: RenderFlex#09969 relayoutBoundary=up5 OVERFLOWING:
  needs compositing
  creator: ColumnPaddingCenterListener_GestureSemanticsRawGestureDetector
    GestureDetectorDebugContainerHomeTabDownloadingOverlayWidgetDeviceShadowListener
    KeyedSubtree-[GlobalKey#1b409] ← ⋯
  parentData: offset=Offset(32.0, 32.0) (can use size)
  constraints: BoxConstraints(0.0<=w<=350.0, 0.0<=h<=740.0)
  size: Size(350.0, 740.0)
  direction: vertical
  mainAxisAlignment: spaceBetween
  mainAxisSize: max
  crossAxisAlignment: center
  verticalDirection: down
◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤

Flex(例えば、ColumnRow)内の子ウィジェットが親ウィジェットが提供する空間より大きくなった場合に発生する。この問題は、ウィジェットツリーが適切にスペースを管理していないことを示している。

Flutter DevToolsのInspectorを使用して、問題を特定する。

flutter runでアプリを実行すると、実行直後にターミナルで、Dart VM ServiceとFlutter DevToolsのための二つのURLが表示される。

Flutter Inspector(ウィジェットツリーの視覚的分析と修正)に関しては、Dart VMではなく、Flutter Devtoolsが提供しているので、2つ目のURLをクリックする。

Dart
http://127.0.0.1:9101?uri=http://127.0.0.1:51945/XXXXXXXXXXX/
" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button">
Flutter run key commands.
r Hot reload. 🔥🔥🔥
R Hot restart.
h List all available interactive commands.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).

A Dart VM Service on KazutakaのiPhone is available at: http://127.0.0.1:51945/XXXXXXXXXX/
The Flutter DevTools debugger and profiler on KazutakaのiPhone is available at:
http://127.0.0.1:9101?uri=http://127.0.0.1:51945/XXXXXXXXXXX/

左から2つ目の「Flutter Inspector」を開くと、現在のウィジェットツリーの全体像が表示される。

上記のエラーメッセージに表示されたウィジェット(このケースではColumnウィジェット)をクリックして選択する。

ターミナルですでにエラーメッセージとしても表示されているが、Columnウィジェットの中の子ウィジェットの合計が1pxはみ出しているよ、とエラー出ている。

ShellScript
A RenderFlex overflowed by 1.00 pixels on the bottom.

コード修正

ちなみに今回のコード(トップ画面)はこちら。

Dart
class HomeTab extends ConsumerStatefulWidget {
  const HomeTab({super.key});

  @override
  _HomeTabState createState() => _HomeTabState();
}

class _HomeTabState extends ConsumerState<HomeTab> {
  // ...

  @override
  Widget build(BuildContext context) {
    // 必要なプロバイダーの読み取りやウォッチはここに含める

    return DebugContainer(
      child: Center(
        child: Padding(
          padding: const EdgeInsets.all(32.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: <Widget>[
              // Overflowエラーを引き起こしている可能性のあるウィジェット
              Align(
                alignment: Alignment.topCenter,
                child: Column(
                  children: [
                    // ... 他のウィジェット

                    // エラーが発生している可能性のあるTopMessageBarウィジェット
                    firmwareUpdate.when(
                      data: (response) {
                        if (response.message == "NEW_FIRMWARE_AVAILABLE") {
                          return TopMessageBar(
                            message:
                                "新しいミーアのバージョン(${response.newFirmwareVersion})がダウンロード可能です",
                            // ... TopMessageBarの他のプロパティ
                          );
                        } else {
                          return SizedBox.shrink();
                        }
                      },
                      // ... ローディングとエラーの状態
                    ),
                    
                    // ... 他のウィジェット
                  ],
                ),
              ),
              
              // ... 他のColumn内のウィジェット
            ],
          ),
        ),
      ),
    );
  }
}

今回の原因は、下記OTAアップデート機能の実装で、新しくFirmwareアップデートのアプリ通知用としてホーム画面のトップにTextウィジェットとして差し込んだことが原因。

Firmwareのアップデートがない場合には、今まで通り通知文言が表示されないので、ウィジェットオーバーフローのエラーが表示されないが、通知がある場合には、その分、子ウィジェットの合計値が大きくなり、親ウィジェット(今回はColumnウィジェット)の幅を超えてしまう。

今回の場合は、Columnウィジェットをさらに、DebugContainerウィジェットで囲み、上下左右に32pxのパディングを指定している。

Dart
padding: const EdgeInsets.all(32.0),

なので、まだコンテンツの縦幅には余裕があるので、Bottomの32pxを0pxに変更することで対応してみる。

EdgeInsetsはFlutterでマージンやパディングを設定する際に使われるクラス。fromLTRBはそのクラスのファクトリメソッドの一つで、それぞれの引数に左(left)、上(top)、右(right)、下(bottom)のマージンやパディングをピクセル単位で指定できる。 https://api.flutter.dev/flutter/painting/EdgeInsets/EdgeInsets.fromLTRB.html

Dart
padding: const EdgeInsets.fromLTRB(32.0, 32.0, 32.0, 0.0),

動作確認と今後の対応

無事、オーバーフローのエラーが解消できた。

今回は、bottomの調整だけで、エラー解消できたが、今後トップ画面に表示したいコンテンツが増えてきた場合の対応も考えておく必要がある。

今回のケースの場合は

  • Snackbarを使用して、他のウィジェットの上に新しいウィジェットレイヤーを表示する。ただSnackbarは画面下部に表示される
  • Overlay クラスを使用して、他のウィジェットの上に新しいウィジェットレイヤーを表示する。
  • Columnウィジェットの子ウィジェットにExpandedFlexibleを適用して、利用可能なスペースに合わせてウィジェットが拡大・縮小されるようにする
  • SingleChildScrollViewを使用して、Columnウィジェットをラップし、ユーザーがコンテンツをスクロールして全てを見られるようにする

などが考えられる。

ただ、今回の場合は、twitterやtiktokみたいに、コンテンツがたくさんあってスクロールによるタイムライン表示が必要というわけではないので、スクロールという手段はUIUXの観点からよくない。

Snackbarは画面下部なので気づきづらいかなと思うので、Overlayクラスを使用する予定。少し調べた限りだが、overlay_supportという、トーストやアプリ内通知を簡単に作成できるパッケージがあった。

https://pub.dev/packages/overlay_support

コメント

タイトルとURLをコピーしました