Scaffold.of () chiamato con un contesto che non contiene Scaffold


184

Come vedi il mio bottone è dentro il corpo di Scaffold. Ma ottengo questa eccezione:

Scaffold.of () chiamato con un contesto che non contiene 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);
}

MODIFICARE:

Ho trovato un'altra soluzione a questo problema. Se diamo a Scaffold una chiave che è GlobalKey, possiamo visualizzare SnackBar come segue senza dover avvolgere il nostro corpo con il widget Builder. Il widget che restituisce Scaffold dovrebbe essere Widget con stato:

 _scaffoldKey.currentState.showSnackBar(snackbar); 

Ho trovato un ottimo tutorial in cui ha creato un wrapper in modo che possa facilmente cambiare o controllare il contesto dell'intera app: noobieprogrammer.blogspot.com/2020/06/…
doppelgunner

Risposte:


245

Questa eccezione si verifica perché si sta utilizzando contextil widget di un'istanza Scaffold. Non contextdi un figlio di Scaffold.

Puoi risolverlo semplicemente usando un contesto diverso:

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

Si noti che mentre stiamo usando Builderqui, questo non è l'unico modo per ottenere un diverso BuildContext.

È anche possibile estrarre la sottostruttura in un diverso Widget(di solito usando extract widgetrefactor)


Ottengo questa eccezione con questo: setState () o markNeedsBuild () chiamato durante la compilazione. I / flutter (21754): questo widget Scaffold non può essere contrassegnato come necessità di compilazione poiché il framework è già nell'I / flutter (21754): processo di creazione dei widget.
Figen Güngör,

1
Il onPressedpassaggio a RaisedButtonnon è una funzione. Modificalo in() => _displaySnackBar(context)
Rémi Rousselet il

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

1
Ho pensato con .of (contesto) che dovrebbe risalire la gerarchia dei widget fino a quando non incontra uno del tipo specificato, in questo caso, Scaffold, che dovrebbe incontrare prima che raggiunga "Widget build (..". Non funziona in questo modo?
CodeGrue

@ RémiRousselet, Quanti tipi di contesto ci sono in Flutter? Come hai detto, contesto di widget, contesto di un figlio di Scaffold.
CopsOnRoad,

121

Puoi usare a GlobalKey. L'unico aspetto negativo è che l'uso di GlobalKey potrebbe non essere il modo più efficiente per farlo.

Un aspetto positivo di questo è che puoi anche passare questa chiave ad altre classi di widget personalizzati che non contengono alcun scaffold. Vedi ( qui )

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

Grazie Lebohang Mbele
Sviluppatore

1
Posso chiedere come replicarlo quando il tuo albero è composto da più widget definiti in più file? _scaffoldKeyè privato a causa del carattere di sottolineatura principale. Ma anche se non lo fosse, è all'interno della HomePageclasse e quindi non disponibile senza un'istanza di una nuova HomePage da qualche parte sotto l'albero, apparentemente creando un riferimento circolare.
ExactaBox,

1
per rispondere alla mia domanda: crea una classe singleton con una GlobalKey<ScaffoldState>proprietà, modifica il costruttore del Widget con l'impalcatura per impostare la proprietà chiave del singleton, quindi nell'albero del widget figlio possiamo chiamare qualcosa come mySingleton.instance.key.currentState.showSnackBar()...
ExactaBox

Perché questo è inefficiente?
user2233706

@ user2233706 Questo è menzionato nella documentazione di GlobalKey qui . Anche questo post potrebbe far luce.
Lebohang Mbele,

43

Due modi per risolvere questo problema

1) Utilizzo del widget 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) Utilizzo di 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

Controlla questo dalla documentazione del metodo:

Quando lo Scaffold viene effettivamente creato nella stessa funzione build, l'argomento contestuale alla funzione build non può essere utilizzato per trovare lo Scaffold (poiché è "sopra" il widget restituito). In questi casi, la seguente tecnica con un Builder può essere utilizzata per fornire un nuovo ambito con un BuildContext che è "sotto" l'impalcatura:

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

È possibile controllare la descrizione dai documenti del metodo


13

Un modo semplice per risolvere questo problema sarà creare una chiave per il tuo scaffold come questo finale con il seguente codice:

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

Scecond: assegna la chiave al tuo Scaffold key: _scaffoldKey

Terzo: chiama la Snackbar usando _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Welcome")));


3

Lo stesso comportamento che si sta verificando viene anche chiamato "caso complicato" nella documentazione di Flutter .

Come risolvere

Il problema è stato risolto in diversi modi, come puoi vedere dalle altre risposte pubblicate qui. Ad esempio, la documentazione a cui mi riferisco risolve il problema utilizzando una funzione Builderche crea

un interno in BuildContextmodo che i onPressedmetodi possano fare riferimento al Scaffoldcon Scaffold.of().

Quindi sarebbe un modo per chiamare showSnackBarda Scaffold

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

Ora qualche dettaglio per il curioso lettore

Io stesso ho trovato abbastanza istruttivo esplorare la documentazione di Flutter semplicemente ( Android Studio ) impostando il cursore su un pezzo di codice ( Flutter classe , metodo, ecc.) E premendo ctrl + B per mostrare la documentazione per quel pezzo specifico.

Il problema particolare che stai affrontando è menzionato nel documento per BuildContext , dove può essere letto

Ogni widget ha il suo BuildContext , che diventa il genitore del widget restituito dalla funzione [...].

Quindi, questo significa che nel nostro caso il contesto sarà il genitore del nostro widget Scaffold quando verrà creato (!). Inoltre, il documento per Scaffold.of afferma che ritorna

Lo stato dall'istanza [ Scaffold ] più vicina di questa classe che racchiude il contesto dato.

Ma nel nostro caso, il contesto non racchiude (ancora) uno Scaffold (non è stato ancora costruito). C'è dove Builder entra in azione!

Ancora una volta, il documento ci illumina. Lì possiamo leggere

[La classe Builder è semplicemente] Un widget platonico che chiama una chiusura per ottenere il suo widget figlio.

Ehi, aspetta un momento, cosa !? Ok, lo ammetto: questo non aiuta molto ... Ma è sufficiente dirlo (seguendo un altro thread SO )

Lo scopo della classe Builder è semplicemente quello di creare e restituire widget figlio.

Quindi ora tutto diventa chiaro! Chiamando Builder all'interno di Scaffold stiamo costruendo lo Scaffold per essere in grado di ottenere il suo contesto, e armato con quel InnerContext possiamo finalmente chiamare Scaffold.of (innerContext)

Segue una versione annotata del codice sopra

@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

Non mi preoccuperei di usare lo snackbar predefinito, perché puoi importare un pacchetto flushbar, che consente una maggiore personalizzazione:

https://pub.dev/packages/flushbar

Per esempio:

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

0

Una soluzione più efficiente è quella di dividere la funzione di generazione in più widget. Ciò introduce un "nuovo contesto", dal quale è possibile ottenere 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

Estrarre il widget del pulsante che visualizzerà snackbar.

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

Come si confronta il tuo approccio con le risposte esistenti? C'è un motivo per scegliere questo approccio piuttosto che la risposta accettata?
Jeremy Caney,

0

qui usiamo un builder per avvolgere un altro widget dove abbiamo bisogno di snackbar

Builder(builder: (context) => GestureDetector(
    onTap: () {
        Scaffold.of(context).showSnackBar(SnackBar(
            content: Text('Your Services have been successfully created Snackbar'),
        ));
        
    },
    child: Container(...)))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.