Scaffold.of () wird mit einem Kontext aufgerufen, der kein Scaffold enthält


181

Wie Sie sehen können, befindet sich mein Knopf in Scaffolds Körper. Aber ich bekomme diese Ausnahme:

Scaffold.of () wird mit einem Kontext aufgerufen, der kein Scaffold enthält.

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);
}

BEARBEITEN:

Ich habe eine andere Lösung für dieses Problem gefunden. Wenn wir Scaffold einen Schlüssel geben, der GlobalKey ist, können wir die SnackBar wie folgt anzeigen, ohne unseren Körper mit dem Builder-Widget umschließen zu müssen. Das Widget, das Scaffold zurückgibt, sollte jedoch ein Stateful-Widget sein:

 _scaffoldKey.currentState.showSnackBar(snackbar); 

Ich habe ein sehr gutes Tutorial gefunden, in dem er einen Wrapper erstellt hat, damit er den Kontext der gesamten App leicht ändern oder steuern kann: noobieprogrammer.blogspot.com/2020/06/…
doppelgunner

Antworten:


242

Diese Ausnahme tritt auf, weil Sie das contextdes instanziierten Widgets verwenden Scaffold. Nicht das contexteines Kindes von Scaffold.

Sie können dies lösen, indem Sie einfach einen anderen Kontext verwenden:

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'),
            ),
        ),
    ),
);

Beachten Sie, dass Builderdies nicht die einzige Möglichkeit ist, eine andere zu erhalten , während wir hier verwenden BuildContext.

Es ist auch möglich, den Teilbaum in einen anderen zu extrahieren Widget(normalerweise mit extract widgetRefactor).


Ich bekomme diese Ausnahme mit folgendem: setState () oder markNeedsBuild (), die während des Builds aufgerufen werden. I / Flutter (21754): Dieses Scaffold-Widget kann nicht als erstellt werden, da sich das Framework bereits im I / Flutter (21754): Prozess zum Erstellen von Widgets befindet.
Figen Güngör

1
Das, an das onPressedSie übergeben haben, RaisedButtonist keine Funktion. Ändern Sie es zu() => _displaySnackBar(context)
Rémi Rousselet

Gotcha: onPressed: () {_displaySnackBar (Kontext);}, Danke =)
Figen Güngör

1
Ich dachte, mit .of (Kontext) soll es die Widget-Hierarchie nach oben verschieben, bis es auf einen der angegebenen Typen stößt, in diesem Fall Scaffold, auf den es stoßen sollte, bevor es "Widget-Build (.." erreicht. Funktioniert es nicht auf diese Weise?
CodeGrue

@ RémiRousselet, Wie viele Arten von Kontext gibt es in Flutter? Wie Sie sagten, Kontext des Widgets, Kontext eines Kindes von Scaffold.
CopsOnRoad

120

Sie können eine verwenden GlobalKey. Der einzige Nachteil ist, dass die Verwendung von GlobalKey möglicherweise nicht die effizienteste Methode ist.

Eine gute Sache dabei ist, dass Sie diesen Schlüssel auch an andere benutzerdefinierte Widgets-Klassen übergeben können, die kein Gerüst enthalten. Siehe ( hier )

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
  }
}

Vielen Dank Lebohang Mbele
Entwickler

1
Kann ich fragen, wie dies repliziert werden soll, wenn Ihr Baum aus mehreren Widgets besteht, die in mehreren Dateien definiert sind? _scaffoldKeyist aufgrund des führenden Unterstrichs privat. Aber selbst wenn dies nicht der HomePageFall wäre , befindet es sich innerhalb der Klasse und ist daher nicht verfügbar, ohne dass irgendwo im Baum eine neue HomePage instanziiert wird, die scheinbar einen Zirkelverweis erstellt.
ExactaBox

1
Um meine eigene Frage zu beantworten: Erstellen Sie eine Singleton-Klasse mit einer GlobalKey<ScaffoldState>Eigenschaft, bearbeiten Sie den Konstruktor des Widgets mit dem Gerüst, um die Schlüsseleigenschaft des Singletons mySingleton.instance.key.currentState.showSnackBar()
festzulegen. Dann

Warum ist das ineffizient?
user2233706

@ user2233706 Dies wird in der Dokumentation von GlobalKey hier erwähnt . Auch dieser Beitrag könnte mehr Licht ins Dunkel bringen.
Lebohang Mbele

43

Zwei Möglichkeiten, um dieses Problem zu lösen

1) Verwenden des Builder-Widgets

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) Verwenden von 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

Überprüfen Sie dies aus der Dokumentation der Methode:

Wenn das Gerüst tatsächlich in derselben Erstellungsfunktion erstellt wird, kann das Kontextargument für die Erstellungsfunktion nicht zum Auffinden des Gerüsts verwendet werden (da es "über" dem zurückgegebenen Widget liegt). In solchen Fällen kann die folgende Technik mit einem Builder verwendet werden, um einen neuen Bereich mit einem BuildContext bereitzustellen, der sich "unter" dem Gerüst befindet:

@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!'),
              ));
            },
          ),
        );
      },
    ),
  );
}

Sie können die Beschreibung von der Prüfung der Methode docs


13

Eine einfache Möglichkeit, dieses Problem zu lösen, besteht darin, einen Schlüssel für Ihr Gerüst wie dieses Finale mit dem folgenden Code zu erstellen:

Zuerst: GlobalKey<ScaffoldState>() _scaffoldKey = GlobalKey<ScaffoldState> ();

Sekunde: Weisen Sie den Schlüssel Ihrem Gerüst zu key: _scaffoldKey

Drittens: Rufen Sie die Snackbar mit _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Welcome")));


3

Das Verhalten, das Sie erleben, wird in der Flutter- Dokumentation sogar als "kniffliger Fall" bezeichnet .

Wie repariert man

Das Problem wird auf verschiedene Arten behoben, wie Sie aus anderen hier veröffentlichten Antworten ersehen können. Zum Beispiel löst die Dokumentation, auf die ich mich beziehe, das Problem durch die Verwendung von a, Builderdas erstellt

ein inneres, BuildContextdamit sich die onPressedMethoden auf das Scaffoldmit beziehen können Scaffold.of().

Ein Weg, um showSnackBarvon Scaffold aus anzurufen, wäre also

@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.')
            ));
          }
        );
      }
    )
  );
}

Nun ein Detail für den neugierigen Leser

Ich selbst fand es sehr lehrreich, die Flutter- Dokumentation zu erkunden, indem ich einfach ( Android Studio ) den Cursor auf einen Code ( Flutter- Klasse, Methode usw.) setzte und Strg + B drückte, um die Dokumentation für diesen bestimmten Teil anzuzeigen.

Das spezielle Problem, mit dem Sie konfrontiert sind, wird im Dokument für BuildContext erwähnt , in dem es gelesen werden kann

Jedes Widget verfügt über einen eigenen BuildContext , der zum übergeordneten Element des Widgets wird, das von der Build-Funktion [...] zurückgegeben wird.

So bedeutet dies , dass in unserem Fall Zusammenhang wird die Mutter unseres Scaffold - Widget , wenn es erstellt wird (!). Weiter sagt das Dokument für Scaffold.of , dass es zurückkehrt

Der Status der nächsten [ Scaffold ] -Instanz dieser Klasse, die den angegebenen Kontext einschließt.

In unserem Fall enthält der Kontext jedoch (noch) kein Gerüst (es wurde noch nicht erstellt). Hier kommt Builder zum Einsatz!

Wieder einmal beleuchtet uns das Dokument. Dort können wir lesen

[Die Builder-Klasse ist einfach] Ein platonisches Widget, das einen Abschluss aufruft, um sein untergeordnetes Widget zu erhalten.

Hey, warte einen Moment, was? Ok, ich gebe zu: das hilft nicht viel ... Aber es reicht zu sagen (nach einem anderen SO-Thread ), dass

Der Zweck der Builder- Klasse besteht einfach darin, untergeordnete Widgets zu erstellen und zurückzugeben.

Jetzt wird alles klar! Indem wir Builder in Scaffold aufrufen , erstellen wir das Scaffold, um seinen eigenen Kontext zu erhalten, und mit diesem inneren Kontext können wir schließlich Scaffold.of (innerContext) aufrufen.

Eine kommentierte Version des obigen Codes folgt

@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

Ich würde die Standard-Snackbar nicht verwenden, da Sie ein Flushbar-Paket importieren können, das eine bessere Anpassbarkeit ermöglicht:

https://pub.dev/packages/flushbar

Beispielsweise:

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

0

Eine effizientere Lösung besteht darin, Ihre Build-Funktion in mehrere Widgets aufzuteilen. Dies führt einen "neuen Kontext" ein, aus dem Sie Scaffold erhalten können

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

Extrahieren Sie Ihr Schaltflächen-Widget, das die Snackbar anzeigt.

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'),
                  );
  }
}

Wie ist Ihr Ansatz im Vergleich zu den vorhandenen Antworten? Gibt es einen Grund, diesen Ansatz beispielsweise der akzeptierten Antwort vorzuziehen?
Jeremy Caney

0

Hier verwenden wir einen Builder, um ein anderes Widget einzuschließen, in dem wir eine Snackbar benötigen

Builder(builder: (context) => GestureDetector(
    onTap: () {
        Scaffold.of(context).showSnackBar(SnackBar(
            content: Text('Your Services have been successfully created Snackbar'),
        ));
        
    },
    child: Container(...)))
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.