Scaffoldを含まないコンテキストで呼び出されたScaffold.of()


184

ご覧のとおり、私のボタンはスキャフォールドの体の中にあります。しかし、私はこの例外を受け取ります:

Scaffold.of()がScaffoldを含まないコンテキストで呼び出されました。

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('SnackBar Playground'),
      ),
      body: Center(
        child: RaisedButton(
          color: Colors.pink,
          textColor: Colors.white,
          onPressed: _displaySnackBar(context),
          child: Text('Display SnackBar'),
        ),
      ),
    );
  }
}

_displaySnackBar(BuildContext context) {
  final snackBar = SnackBar(content: Text('Are you talkin\' to me?'));
  Scaffold.of(context).showSnackBar(snackBar);
}

編集:

この問題の別の解決策を見つけました。ScaffoldにGlobalKeyであるキーを与えると、ボディをビルダーウィジェットでラップする必要なく、次のようにSnackBarを表示できます。Scaffoldを返すウィジェットは、ステートフルウィジェットである必要があります。

 _scaffoldKey.currentState.showSnackBar(snackbar); 

:私は彼が簡単にアプリケーション全体のコンテキストを変更したり、制御することができるように、彼はラッパーを作った非常に良いチュートリアル見つけnoobieprogrammer.blogspot.com/2020/06/...
doppelgunner

回答:


245

この例外contextは、インスタンス化したウィジェットのを使用しているために発生しますScaffold。のcontext子ではありませんScaffold

別のコンテキストを使用するだけでこれを解決できます:

Scaffold(
    appBar: AppBar(
        title: Text('SnackBar Playground'),
    ),
    body: Builder(
        builder: (context) => 
            Center(
            child: RaisedButton(
            color: Colors.pink,
            textColor: Colors.white,
            onPressed: () => _displaySnackBar(context),
            child: Text('Display SnackBar'),
            ),
        ),
    ),
);

Builderここで使用している間、これが別のを取得する唯一の方法ではないことに注意してくださいBuildContext

サブツリーを別のものに抽出することも可能ですWidget(通常はextract widgetリファクタリングを使用)


ビルド中にsetState()またはmarkNeedsBuild()が呼び出されると、この例外が発生します。I / flutter(21754):このScaffoldウィジェットは、フレームワークがすでにウィジェットの構築プロセスであるI / flutter(21754)にあるため、構築する必要があるとマークできません。
FigenGüngör18年

1
onPressedあなたが渡されたRaisedButton関数ではありません。変更してください() => _displaySnackBar(context)
レミRousselet

Gotcha:onPressed:(){_displaySnackBar(context);}、ありがとう=)
FigenGüngör2018

1
.of(context)を使用すると、指定されたタイプの1つ(この場合はScaffold)に遭遇するまでウィジェット階層を上に移動することになっていると思いました。 this way?
CodeGrue

@RémiRousselet、Flutterには何種類のコンテキストがありますか?あなたがウィジェットのコンテキスト、足場の子のコンテキストを言ったように。
CopsOnRoad 2018

121

を使用できますGlobalKey。唯一の欠点は、GlobalKeyを使用することがこれを行う最も効率的な方法ではない可能性があることです。

これの良いところは、このキーを、足場を含まない他のカスタムウィジェットクラスに渡すこともできることです。見る(こちら

class HomePage extends StatelessWidget {
  final _scaffoldKey = GlobalKey<ScaffoldState>(); \\ new line
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,                           \\ new line
      appBar: AppBar(
        title: Text('SnackBar Playground'),
      ),
      body: Center(
        child: RaisedButton(
          color: Colors.pink,
          textColor: Colors.white,
          onPressed: _displaySnackBar(context),
          child: Text('Display SnackBar'),
        ),
      ),
    );
  }
  _displaySnackBar(BuildContext context) {
    final snackBar = SnackBar(content: Text('Are you talkin\' to me?'));
    _scaffoldKey.currentState.showSnackBar(snackBar);   \\ edited line
  }
}

おかげでLebohang Mbele
開発者

1
ツリーが複数のファイルで定義された複数のウィジェットで構成されている場合、これを複製する方法を尋ねることはできますか?_scaffoldKey先頭のアンダースコアのため、プライベートです。しかし、そうでなかったとしても、HomePageクラス内にあるため、ツリーのどこかに新しいHomePageをインスタンス化せずに使用できず、循環参照を作成しているように見えます。
ExactaBox、

1
私自身の質問に答えるには、GlobalKey<ScaffoldState>プロパティを使用してシングルトンクラスを作成し、スキャフォールドを使用してウィジェットのコンストラクターを編集して、シングルトンのキープロパティを設定します。次に、子ウィジェットのツリーで次のように呼び出すことができますmySingleton.instance.key.currentState.showSnackBar()...
ExactaBox

なぜこれが非効率なのですか?
user2233706

@ user2233706これは、ここのGlobalKeyのドキュメントで言及されています。また、この投稿はもっと光を当てるかもしれません。
Lebohang Mbele

43

この問題を解決する2つの方法

1)Builderウィジェットの使用

Scaffold(
    appBar: AppBar(
        title: Text('My Profile'),
    ),
    body: Builder(
        builder: (ctx) => RaisedButton(
            textColor: Colors.red,
            child: Text('Submit'),
            onPressed: () {
                 Scaffold.of(ctx).showSnackBar(SnackBar(content: Text('Profile Save'),),);
            }               
        ),
    ),
);

2)GlobalKeyの使用

class HomePage extends StatelessWidget {

  final globalKey = GlobalKey<ScaffoldState>();

  @override
  Widget build(BuildContext context) {
     return Scaffold(
       key: globalKey,
       appBar: AppBar(
          title: Text('My Profile'),
       ),
       body:  RaisedButton(
          textColor: Colors.red,
          child: Text('Submit'),
          onPressed: (){
               final snackBar = SnackBar(content: Text('Profile saved'));
               globalKey.currentState.showSnackBar(snackBar);
          },
        ),
     );
   }
}

16

メソッドのドキュメントからこれを確認してください:

Scaffoldが実際に同じビルド関数で作成されている場合、ビルド関数へのコンテキスト引数を使用してScaffoldを見つけることはできません(返されるウィジェットの「上」にあるため)。このような場合、ビルダーを使用する次の手法を使用して、足場の「下」にあるBuildContextで新しいスコープを提供できます。

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('Demo')
    ),
    body: Builder(
      // Create an inner BuildContext so that the onPressed methods
      // can refer to the Scaffold with Scaffold.of().
      builder: (BuildContext context) {
        return Center(
          child: RaisedButton(
            child: Text('SHOW A SNACKBAR'),
            onPressed: () {
              Scaffold.of(context).showSnackBar(SnackBar(
                content: Text('Hello!'),
              ));
            },
          ),
        );
      },
    ),
  );
}

メソッドのドキュメントから説明を確認できます


13

この問題を解決する簡単な方法は、次のコードを使用して、このfinalのようなスキャフォールドのキーを作成することです。

最初: GlobalKey<ScaffoldState>() _scaffoldKey = GlobalKey<ScaffoldState> ();

Scecond:足場にキーを割り当てます key: _scaffoldKey

3番目:を使用してスナックバーを呼び出す _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Welcome")));


3

Flutterの ドキュメントでは、まさに発生している動作を「トリッキーなケース」と呼んでいます

直し方

ここに投稿された他の回答からわかるように、この問題はさまざまな方法で修正されています。たとえば、私が参照するドキュメントの一部は、Builder作成するa を使用して問題を解決します

メソッドがwithを参照できるBuildContextように、内部。onPressedScaffoldScaffold.of()

このように呼び出す方法showSnackBarから足場になります

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('Demo')),
    body: Builder(
      builder: (BuildContext innerContext) {
        return FlatButton(
          child: Text('BUTTON'),
          onPressed: () {
            Scaffold.of(innerContext).showSnackBar(SnackBar(
              content: Text('Hello.')
            ));
          }
        );
      }
    )
  );
}

好奇心の強い読者のために

私自身、コードの一部(Flutterクラス、メソッドなど)にカーソルを設定し(Flutterクラス、メソッドなど)、その特定の部分のドキュメントを表示するために(Android Studio)だけでFlutterのドキュメントを探索することは非常に有益でした。

あなたが直面している特定の問題は、BuildContextのドキュメントに記載されています。

各ウィジェットには独自のBuildContextがあり、[...]。build関数によって返されるウィジェットの親になります。

つまり、これは、私たちの場合、コンテキスト が作成時にスキャフォールドウィジェットの親になることを意味します(!)。さらに、Scaffold.ofのドキュメントには、

指定されたコンテキストを囲む、このクラスの最も近い[ Scaffold ]インスタンスからの状態。

しかし、私たちの場合、コンテキスト足場を(まだ)囲んでいません(まだ作成されていません)。Builderが動作するところがあります!

もう一度、ドキュメントは私たちを照らします。読める

[Builderクラスは単純です]クロージャを呼び出してその子ウィジェットを取得するプラトニックウィジェット。

ねえ、ちょっと待って、何!?OK、私は認めます:それは多くの助けにはなりません...しかし、(別のSOスレッドに従って)

Builderクラスの目的は、単に子ウィジェットを作成して返すことです。

だから今、すべてが明らかになります!Scaffold内でBuilderを呼び出すことにより、独自のコンテキストを取得できるようにScaffoldを構築し、そのinnerContextを利用して、最終的にScaffold.of(innerContext)を呼び出すことができます。

上記のコードの注釈付きバージョンは次のとおりです

@override
Widget build(BuildContext context) {
  // here, Scaffold.of(context) returns null
  return Scaffold(
    appBar: AppBar(title: Text('Demo')),
    body: Builder(
      builder: (BuildContext innerContext) {
        return FlatButton(
          child: Text('BUTTON'),
          onPressed: () {
            // here, Scaffold.of(innerContext) returns the locally created Scaffold
            Scaffold.of(innerContext).showSnackBar(SnackBar(
              content: Text('Hello.')
            ));
          }
        );
      }
    )
  );
}

1

デフォルトのスナックバーを使用する必要はありません。これは、フラッシュバーパッケージをインポートできるため、カスタマイズ性が向上するためです。

https://pub.dev/packages/flushbar

例えば:

Flushbar(
                  title:  "Hey Ninja",
                  message:  "Lorem Ipsum is simply dummy text of the printing and typesetting industry",
                  duration:  Duration(seconds: 3),              
                )..show(context);

0

より効率的なソリューションは、ビルド関数をいくつかのウィジェットに分割することです。これにより、「新しいコンテキスト」が導入され、そこからScaffoldを取得できます

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Scaffold.of example.')),
        body: MyScaffoldBody(),
      ),
    );
  }
}

class MyScaffoldBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: RaisedButton(
          child: Text('Show a snackBar'),
          onPressed: () {
            Scaffold.of(context).showSnackBar(
              SnackBar(
                content: Text('Have a Snack'),
              ),
            );
          }),
    );
  }
}

0

スナックバーを表示するボタンウィジェットを抽出します。

class UsellesslyNestedButton extends StatelessWidget {
  const UsellesslyNestedButton({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      onPressed: (){
        showDefaultSnackbar(context);
                    },
                    color: Colors.blue,
                    child: Text('Show about'),
                  );
  }
}

あなたのアプローチは既存の答えとどのように比較しますか?受け入れられた答えなど、このアプローチを選択する理由はありますか?
Jeremy Caney

0

ここでは、ビルダーを使用してスナックバーが必要な別のウィジェットをラップします

Builder(builder: (context) => GestureDetector(
    onTap: () {
        Scaffold.of(context).showSnackBar(SnackBar(
            content: Text('Your Services have been successfully created Snackbar'),
        ));
        
    },
    child: Container(...)))
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.