💻

[Flutter] SnackBarを使おうとすると Scaffold.of() called with(略)と言われるときの対応

2020/08/09に公開

小ネタです。

Flutterで SnackBar を使おうとしたら

Scaffold.of() called with a context that does not contain a Scaffold.

みたいな感じで怒られたことありますよね。

ちなみにエラーの全文は以下。

══╡ EXCEPTION CAUGHT BY GESTURE ╞═══════════════════════════════════════════════════════════════════
The following assertion was thrown while handling a gesture:
Scaffold.of() called with a context that does not contain a Scaffold.
No Scaffold ancestor could be found starting from the context that was passed to Scaffold.of(). This
usually happens when the context provided is from the same StatefulWidget as that whose build
function actually creates the Scaffold widget being sought.
There are several ways to avoid this problem. The simplest is to use a Builder to get a context that
is "under" the Scaffold. For an example of this, please see the documentation for Scaffold.of():
  https://api.flutter.dev/flutter/material/Scaffold/of.html
A more efficient solution is to split your build function into several widgets. This introduces a
new context from which you can obtain the Scaffold. In this solution, you would have an outer widget
that creates the Scaffold populated by instances of your new inner widgets, and then in these inner
widgets you would use Scaffold.of().
A less elegant but more expedient solution is assign a GlobalKey to the Scaffold, then use the
key.currentState property to obtain the ScaffoldState rather than using the Scaffold.of() function.
The context used was:
  Settings
When the exception was thrown, this was the stack:
#0      Scaffold.of 
package:flutter/…/material/scaffold.dart:1451
#1      _SettingsState.build.<anonymous closure> 
package:match/pages/settings.dart:86
#2      _InkResponseState._handleTap 
package:flutter/…/material/ink_well.dart:992
#3      _InkResponseState.build.<anonymous closure> 
package:flutter/…/material/ink_well.dart:1098
#4      GestureRecognizer.invokeCallback 
package:flutter/…/gestures/recognizer.dart:184
#5      TapGestureRecognizer.handleTapUp 
package:flutter/…/gestures/tap.dart:524
#6      BaseTapGestureRecognizer._checkUp 
package:flutter/…/gestures/tap.dart:284
#7      BaseTapGestureRecognizer.acceptGesture 
package:flutter/…/gestures/tap.dart:256
#8      GestureArenaManager.sweep 
package:flutter/…/gestures/arena.dart:158

このような場合の対策についてです。

結論としては、内部で SnackBar を使いたいウィジェットを Builder でラップしてあげて、新しい BuildContext を使って SnackBar を出力してあげればエラーになりません。

Before


Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('ほげ'),
    ),
    body: Center(
      child: RaisedButton(
        child: Text('保存'),
        onPressed: () {
          Scaffold.of(context).showSnackBar(SnackBar(
            content: Text('SnackBarは正常に動作しません'),
          )),
        },
      ),
    ),
  );
}

After


Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('ほげ'),
    ),
    body: Builder(
      builder: (BuildContext context) {
        return Center(
          child: RaisedButton(
            child: Text('保存'),
            onPressed: () {
              Scaffold.of(context).showSnackBar(SnackBar(
                content: Text('SnackBarが正常に動作します'),
              ));
            },
          ),
        );
      },
    ),
  );
}

こんな感じ。

参考:flutter - Scaffold.of() called with a context that does not contain a Scaffold - Stack Overflow

他にも

  • SnackBar の代わりに fluttertoast パッケージを使う
  • get パッケージを使って BuildContext 不要で SnackBar を出力できるようにする

といった方法も検討してみてもいいかもしれません。

get ライブラリを使う場合は、ライブラリをインストールするだけではなく、公式ドキュメントStep 1 Step 2 Step 3 の手順で準備をしないと動作しないので要注意です。

GitHubで編集を提案

Discussion