Scaffold.of () appelé avec un contexte qui ne contient pas de Scaffold

188

Comme vous pouvez le voir, mon bouton est à l'intérieur du corps de Scaffold. Mais j'obtiens cette exception:

Scaffold.of () appelé avec un contexte qui ne contient pas de 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);
}

ÉDITER:

J'ai trouvé une autre solution à ce problème. Si nous donnons à Scaffold une clé qui est GlobalKey, nous pouvons afficher le SnackBar comme suit sans avoir besoin d'envelopper notre corps avec le widget Builder. Le widget qui renvoie Scaffold doit être un widget avec état:

 _scaffoldKey.currentState.showSnackBar(snackbar); 
Figen Güngör
la source
J'ai trouvé un très bon tutoriel où il a fait un wrapper pour qu'il puisse facilement changer ou contrôler le contexte de toute l'application: noobieprogrammer.blogspot.com/2020/06/…
doppelgunner

Réponses:

249

Cette exception se produit car vous utilisez contextle widget qui a instancié Scaffold. Pas celui contextd'un enfant de Scaffold.

Vous pouvez résoudre ce problème en utilisant simplement un contexte différent:

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

Notez que pendant que nous utilisons Builderici, ce n'est pas le seul moyen d'obtenir un fichier différent BuildContext.

Il est également possible d'extraire le sous-arbre dans un autre Widget(généralement en utilisant extract widgetrefactor)

Rémi Rousselet
la source
J'obtiens cette exception avec ceci: setState () ou markNeedsBuild () appelé pendant la construction. I / flutter (21754): Ce widget Scaffold ne peut pas être marqué comme devant être construit car le framework est déjà dans le I / flutter (21754): processus de construction des widgets.
Figen Güngör
1
Le que onPressedvous avez passé RaisedButtonn'est pas une fonction. Changer pour() => _displaySnackBar(context)
Rémi Rousselet
Gotcha: onPressed: () {_displaySnackBar (context);}, Thanks =)
Figen Güngör
1
Je pensais qu'avec .of (context) il était censé remonter la hiérarchie des widgets jusqu'à ce qu'il rencontre l'un du type donné, dans ce cas, Scaffold, qu'il devrait rencontrer avant d'atteindre "Widget build (..". Cela ne fonctionne pas par ici?
CodeGrue
@ RémiRousselet, Combien de types de contexte existe-t-il dans Flutter? Comme tu l'as dit, contexte de widget, contexte d'un enfant de Scaffold.
CopsOnRoad
121

Vous pouvez utiliser un fichier GlobalKey. Le seul inconvénient est que l'utilisation de GlobalKey n'est peut-être pas le moyen le plus efficace de le faire.

Une bonne chose à ce sujet est que vous pouvez également passer cette clé à d'autres classes de widgets personnalisés qui ne contiennent aucun échafaudage. Voir ( ici )

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
la source
Merci Lebohang Mbele
Développeur
1
Puis-je demander comment reproduire cela lorsque votre arborescence est composée de plusieurs widgets définis dans plusieurs fichiers? _scaffoldKeyest privé en raison du trait de soulignement principal. Mais même si ce n'était pas le cas, il est à l'intérieur de la HomePageclasse et donc indisponible sans instancier une nouvelle page d'accueil quelque part dans l'arborescence, créant apparemment une référence circulaire.
ExactaBox
1
pour répondre à ma propre question: créez une classe singleton avec une GlobalKey<ScaffoldState>propriété, éditez le constructeur du widget avec l'échafaudage pour définir la propriété clé du singleton, puis dans l'arborescence du widget enfant, nous pouvons appeler quelque chose comme mySingleton.instance.key.currentState.showSnackBar()...
ExactaBox
Pourquoi est-ce inefficace?
user2233706
@ user2233706 Ceci est mentionné dans la documentation de GlobalKey ici . De plus ce poste pourrait jeter plus de lumière.
Lebohang Mbele
44

Deux façons de résoudre ce problème

1) Utilisation du 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) Utilisation de 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);
          },
        ),
     );
   }
}
Sanjayrajsinh
la source
16

Vérifiez cela dans la documentation de la méthode:

Lorsque l'échafaudage est en fait créé dans la même fonction de construction, l'argument de contexte de la fonction de construction ne peut pas être utilisé pour trouver l'échafaudage (puisqu'il est "au-dessus" du widget renvoyé). Dans de tels cas, la technique suivante avec un générateur peut être utilisée pour fournir une nouvelle étendue avec un BuildContext qui est "sous" l'échafaudage:

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

Vous pouvez vérifier la description de la de la méthode docs

SANAT
la source
13

Un moyen simple de résoudre ce problème sera de créer une clé pour votre échafaudage comme cette finale avec le code suivant:

Première: GlobalKey<ScaffoldState>() _scaffoldKey = GlobalKey<ScaffoldState> ();

Scecond: attribuez la clé à votre échafaudage key: _scaffoldKey

Troisièmement: appelez le Snackbar en utilisant _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Welcome")));

Mohamed Abdul-Malik
la source
3

Le comportement même que vous rencontrez est même qualifié de "cas délicat" dans la documentation Flutter .

Comment réparer

Le problème est résolu de différentes manières, comme vous pouvez le voir dans d'autres réponses publiées ici. Par exemple, la documentation à laquelle je fais référence résout le problème en utilisant un Builderqui crée

un interne BuildContextafin que les onPressedméthodes puissent faire référence au Scaffoldavec Scaffold.of().

Ainsi, un moyen d'appeler showSnackBardepuis Scaffold serait

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

Maintenant quelques détails pour le lecteur curieux

J'ai moi-même trouvé assez instructif d'explorer la documentation Flutter simplement ( Android Studio ) en plaçant le curseur sur un morceau de code ( classe Flutter , méthode, etc.) et en appuyant sur ctrl + B pour afficher la documentation de ce morceau spécifique.

Le problème particulier auquel vous êtes confronté est mentionné dans le docu de BuildContext , où peut être lu

Chaque widget a son propre BuildContext , qui devient le parent du widget retourné par la fonction [...]. Build .

Donc, cela signifie que dans notre cas, le contexte sera le parent de notre widget Scaffold lors de sa création (!). De plus, le docu pour Scaffold.of dit qu'il renvoie

L'état de l' instance [ Scaffold ] la plus proche de cette classe qui englobe le contexte donné.

Mais dans notre cas, le contexte ne renferme pas (encore) un échafaudage (il n'a pas encore été construit). C'est là que Builder entre en action!

Une fois de plus, le docu nous éclaire. Là on peut lire

[La classe Builder, est simplement] Un widget platonique qui appelle une fermeture pour obtenir son widget enfant.

Hé, attendez un instant, quoi!? Ok, j'avoue: ça n'aide pas beaucoup ... Mais il suffit de dire (suite à un autre fil SO ) que

Le but de la classe Builder est simplement de créer et de renvoyer des widgets enfants.

Alors maintenant, tout devient clair! En appelant Builder à l' intérieur de Scaffold, nous construisons le Scaffold afin de pouvoir obtenir son propre contexte, et armés de ce innerContext, nous pouvons enfin appeler Scaffold.of (innerContext)

Une version annotée du code ci-dessus suit

@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.')
            ));
          }
        );
      }
    )
  );
}
Deczaloth
la source
1

Je ne prendrais pas la peine d'utiliser le snack par défaut, car vous pouvez importer un package flushbar, ce qui permet une plus grande personnalisation:

https://pub.dev/packages/flushbar

Par exemple:

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

Une solution plus efficace consiste à diviser votre fonction de construction en plusieurs widgets. Cela introduit un `` nouveau contexte '', à partir duquel vous pouvez obtenir 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'),
              ),
            );
          }),
    );
  }
}
TDM
la source
0

Extrayez votre widget bouton qui affichera le 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'),
                  );
  }
}
Rashid
la source
Comment votre approche se compare-t-elle aux réponses existantes? Y a-t-il une raison de choisir cette approche plutôt que, disons, la réponse acceptée?
Jeremy Caney
0

ici, nous utilisons un constructeur pour envelopper un autre widget où nous avons besoin d'un snack

Builder(builder: (context) => GestureDetector(
    onTap: () {
        Scaffold.of(context).showSnackBar(SnackBar(
            content: Text('Your Services have been successfully created Snackbar'),
        ));
        
    },
    child: Container(...)))
Krishna Chaitanya
la source