Scaffold.of () llamado con un contexto que no contiene un Scaffold

184

Como puede ver, mi botón está dentro del cuerpo de Scaffold. Pero me sale esta excepción:

Scaffold.of () llamado con un contexto que no contiene un 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);
}

EDITAR:

Encontré otra solución a este problema. Si le damos a Scaffold una clave que es GlobalKey, podemos mostrar SnackBar de la siguiente manera sin necesidad de envolver nuestro cuerpo con el widget Builder. Sin embargo, el widget que devuelve Scaffold debería ser Stateful widget .:

 _scaffoldKey.currentState.showSnackBar(snackbar); 
Figen Güngör
fuente
Encontré un muy buen tutorial donde hizo un contenedor para poder cambiar o controlar fácilmente el contexto de toda la aplicación: noobieprogrammer.blogspot.com/2020/06/…
doppelgunner

Respuestas:

245

Esta excepción ocurre porque está utilizando contextel widget que instancia Scaffold. No el contextde un hijo de Scaffold.

Puede resolver esto simplemente usando un contexto diferente:

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

Tenga en cuenta que mientras estamos usando Builderaquí, esta no es la única forma de obtener una diferente BuildContext.

También es posible extraer el subárbol en un diferente Widget(generalmente usando extract widgetrefactor)

Rémi Rousselet
fuente
Recibo esta excepción con esto: setState () o markNeedsBuild () llamado durante la compilación. I / flutter (21754): este widget Scaffold no se puede marcar como necesario para construir porque el marco ya está en I / flutter (21754): proceso de creación de widgets.
Figen Güngör
1
Lo onPressedque pasaste RaisedButtonno es una función. Cámbielo a() => _displaySnackBar(context)
Rémi Rousselet
Gotcha: onPressed: () {_displaySnackBar (context);}, Thanks =)
Figen Güngör
1
Pensé que con .of (contexto) se supone que debe subir por la jerarquía de widgets hasta que encuentre uno del tipo dado, en este caso, Scaffold, que debería encontrar antes de llegar a "Widget build (.."). ¿No funciona? por aquí?
CodeGrue
@ RémiRousselet, ¿cuántos tipos de contexto hay en Flutter? Como dijiste contexto de widget, contexto de un hijo de Scaffold.
CopsOnRoad
121

Puedes usar a GlobalKey. El único inconveniente es que usar GlobalKey podría no ser la forma más eficiente de hacerlo.

Lo bueno de esto es que también puede pasar esta clave a otra clase de widgets personalizados que no contienen ningún andamio. Ver ( aquí )

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
fuente
Gracias Lebohang Mbele
Desarrollador
1
¿Puedo preguntar cómo replicar esto cuando su árbol está compuesto por múltiples widgets definidos en múltiples archivos? _scaffoldKeyes privado debido al guión bajo destacado. Pero incluso si no fuera así, está dentro de la HomePageclase y, por lo tanto, no está disponible sin crear una instancia de una nueva página de inicio en algún lugar del árbol, aparentemente creando una referencia circular.
ExactaBox
1
para responder mi propia pregunta: crear una clase singleton con una GlobalKey<ScaffoldState>propiedad, editar el constructor del Widget con el andamio para establecer la propiedad clave del singleton, luego en el árbol del widget secundario podemos llamar algo así como mySingleton.instance.key.currentState.showSnackBar()...
ExactaBox
¿Por qué es esto ineficiente?
user2233706
@ user2233706 Esto se menciona en la documentación de GlobalKey aquí . También esta publicación podría arrojar más luz.
Lebohang Mbele
43

Dos formas de resolver este problema

1) Uso del widget de generador

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) Usando 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
fuente
16

Verifique esto en la documentación del método:

Cuando el Andamio se crea realmente en la misma función de compilación, el argumento de contexto para la función de compilación no se puede utilizar para encontrar el Andamio (ya que está "por encima" del widget que se devuelve). En tales casos, la siguiente técnica con un generador se puede utilizar para proporcionar un nuevo ámbito con un BuildContext que está "debajo" del andamio:

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

Puede consultar la descripción de los documentos del método

SANAT
fuente
13

Una manera simple de resolver este problema será crear una clave para su andamio como esta final con el siguiente código:

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

Scecond: asigna la llave a tu andamio key: _scaffoldKey

Tercero: llame al Snackbar usando _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Welcome")));

Mohamed Abdul-Malik
fuente
3

El mismo comportamiento que está experimentando incluso se conoce como un "caso complicado" en la documentación de Flutter .

Como arreglar

El problema se soluciona de diferentes maneras, como puede ver en otras respuestas publicadas aquí. Por ejemplo, la pieza de documentación a la que me refiero resuelve el problema mediante el uso de un Builderque crea

un interno BuildContextpara que los onPressedmétodos puedan referirse al Scaffoldcon Scaffold.of().

Por lo tanto, una forma de llamar showSnackBardesde Scaffold sería

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

Ahora algunos detalles para el lector curioso

Yo mismo encontré bastante instructivo para explorar la documentación de Flutter simplemente ( Android Studio ) colocando el cursor en una pieza de código ( clase de Flutter , método, etc.) y presionando ctrl + B para mostrar la documentación de esa pieza específica.

El problema particular que enfrenta se menciona en el documento de BuildContext , donde se puede leer

Cada widget tiene su propio BuildContext , que se convierte en el padre del widget devuelto por la [...] función. Build .

Entonces, esto significa que, en nuestro caso, el contexto será el padre de nuestro widget Scaffold cuando se cree (!). Además, el docu para Scaffold.of dice que regresa

El estado de la instancia [ Scaffold ] más cercana de esta clase que encierra el contexto dado.

Pero en nuestro caso, el contexto no encierra (todavía) un Andamio (aún no se ha construido). ¡Ahí es donde entra en acción Builder !

Una vez más, el docu nos ilumina. Allí podemos leer

[La clase Builder, es simplemente] Un widget platónico que llama a un cierre para obtener su widget hijo.

Oye, espera un momento, ¿qué? Ok, lo admito: eso no está ayudando mucho ... Pero es suficiente decir (siguiendo otro hilo SO ) que

El propósito de la clase Builder es simplemente construir y devolver widgets secundarios.

¡Así que ahora todo queda claro! Al llamar a Builder dentro de Scaffold , estamos construyendo el Scaffold para poder obtener su propio contexto, y armados con ese contexto interno , finalmente podemos llamar a Scaffold.of (innerContext)

Sigue una versión anotada del código anterior

@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
fuente
1

No me molestaría en usar la barra de aperitivos predeterminada, porque puede importar un paquete de barra de descarga, que permite una mayor personalización:

https://pub.dev/packages/flushbar

Por ejemplo:

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

Una solución más eficiente es dividir su función de compilación en varios widgets. Esto introduce un 'nuevo contexto', desde el cual puede obtener 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
fuente
0

Extraiga su widget de botón que mostrará 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
fuente
¿Cómo se compara su enfoque con las respuestas existentes? ¿Hay alguna razón para elegir este enfoque sobre, digamos, la respuesta aceptada?
Jeremy Caney
0

aquí usamos un generador para envolver en otro widget donde necesitamos snackbar

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