Scaffold.of () chamado com um contexto que não contém um Scaffold

184

Como você pode ver, meu botão está dentro do corpo do andaime. Mas eu recebo esta exceção:

Scaffold.of () chamado com um contexto que não contém um 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:

Encontrei outra solução para esse problema. Se dermos ao Scaffold uma chave que é GlobalKey, podemos exibir a SnackBar da seguinte forma, sem precisar envolver nosso corpo com o widget Builder. O widget que retorna o Scaffold deve ser um widget Stateful:

 _scaffoldKey.currentState.showSnackBar(snackbar); 
Figen Güngör
fonte
Encontrei um tutorial muito bom em que ele criou um invólucro para poder alterar ou controlar facilmente o contexto de todo o aplicativo: noobieprogrammer.blogspot.com/2020/06/…
doppelgunner

Respostas:

245

Essa exceção acontece porque você está usando contexto widget que foi instanciado Scaffold. Não é contextde um filho deScaffold .

Você pode resolver isso usando apenas um 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'),
            ),
        ),
    ),
);

Observe que enquanto estivermos usando Builderaqui, essa não é a única maneira de obter um método diferente.BuildContext .

Também é possível extrair a subárvore em uma diferente Widget(geralmente usando extract widgetrefator)

Rémi Rousselet
fonte
Eu recebo essa exceção com isso: setState () ou markNeedsBuild () chamado durante a compilação. I / flutter (21754): este widget de andaime não pode ser marcado como necessário para construir, porque a estrutura já está no processo I / flutter (21754): de construção de widgets.
Figen Güngör
1
O que onPressedvocê passou RaisedButtonnão é uma função. Altere para() => _displaySnackBar(context)
Rémi Rousselet
Gotcha: onPressed: () {_displaySnackBar (contexto);}, Graças =)
Figen Güngör
1
Pensei que com .of (context) ele deveria percorrer a hierarquia do widget até encontrar um do tipo especificado, neste caso, Scaffold, que ele deve encontrar antes de atingir "Widget build (..". Não funciona desta forma?
CodeGrue
@ RémiRousselet, Quantos tipos de contexto existem no Flutter? Como você disse, contexto do widget, contexto de um filho do Andaime.
CopsOnRoad
121

Você pode usar a GlobalKey. A única desvantagem é que o uso da GlobalKey pode não ser a maneira mais eficiente de fazer isso.

Uma coisa boa disso é que você também pode passar essa chave para outra classe de widgets personalizados que não contêm nenhum andaime. Veja ( aqui )

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
fonte
Obrigado Lebohang Mbele
Desenvolvedor
1
Posso perguntar como replicar isso quando sua árvore é composta por vários widgets definidos em vários arquivos? _scaffoldKeyé privado devido ao sublinhado principal. Mas, mesmo que não estivesse, está dentro da HomePageclasse e, portanto, não está disponível sem instanciar uma nova HomePage em algum lugar da árvore, aparentemente criando uma referência circular.
ExactaBox
1
para responder à minha própria pergunta: crie uma classe singleton com uma GlobalKey<ScaffoldState>propriedade, edite o construtor do Widget com o andaime para definir a propriedade chave do singleton e, na árvore do widget filho, podemos chamar algo como mySingleton.instance.key.currentState.showSnackBar()...
ExactaBox
Por que isso é ineficiente?
User2233706 6/03
@ user2233706 Isso é mencionado na documentação da GlobalKey aqui . Além disso, este post pode lançar mais luz.
Lebohang Mbele 11/03
43

Duas maneiras de resolver esse problema

1) Usando o widget Construtor

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
fonte
16

Verifique isso na documentação do método:

Quando o Scaffold é realmente criado na mesma função de construção, o argumento de contexto para a função de construção não pode ser usado para encontrar o Andaime (já que está "acima" do widget sendo retornado). Nesses casos, a seguinte técnica com um Construtor pode ser usada para fornecer um novo escopo com um BuildContext "sob" o Andaime:

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

Você pode verificar a descrição nos documentos do método

SANAT
fonte
13

Uma maneira simples de resolver esse problema será criar uma chave para o seu andaime, como esta final, com o seguinte código:

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

Scecond: atribua a chave ao seu andaime key: _scaffoldKey

Terceiro: Ligue para a Snackbar usando _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Welcome")));

Mohamed Abdul-Malik
fonte
3

O próprio comportamento que você está enfrentando é referido como um "caso complicado" na documentação do Flutter .

Como consertar

O problema foi corrigido de maneiras diferentes, como você pode ver em outras respostas postadas aqui. Por exemplo, a documentação a que me refiro resolve o problema usando um Builderque cria

um interno BuildContextpara que os onPressedmétodos possam se referir ao Scaffoldcom Scaffold.of().

Assim, uma maneira de ligar showSnackBardo andaime seria

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

Agora, alguns detalhes para o leitor curioso

Eu mesmo achei bastante instrutivo explorar a documentação do Flutter simplesmente ( Android Studio ) configurando o cursor em um trecho de código ( classe Flutter , método, etc.) e pressionando ctrl + B para exibir a documentação dessa peça específica.

O problema específico que você está enfrentando é mencionado no documento do BuildContext , onde pode ser lido

Cada widget possui seu próprio BuildContext , que se torna o pai do widget retornado pela [...] função. Build.

Portanto, isso significa que, no nosso caso, o contexto será o pai do nosso widget de andaime quando ele for criado (!). Além disso, o documento para Scaffold.of diz que ele retorna

O estado da instância [ Scaffold ] mais próxima desta classe que inclui o contexto especificado.

Mas, no nosso caso, o contexto ainda não inclui um andaime (ele ainda não foi construído). É aí que o Builder entra em ação!

Mais uma vez, o docu nos ilumina. Lá podemos ler

[A classe Builder, é simplesmente] Um widget platônico que chama um fechamento para obter seu widget filho.

Ei, espere um momento, o que !? Ok, admito: isso não está ajudando muito ... Mas basta dizer (seguindo outro tópico do SO ) que

O objetivo da classe Builder é simplesmente criar e retornar widgets filhos.

Então agora tudo fica claro! Ao chamar o Builder dentro do Scaffold , estamos construindo o Scaffold para obter seu próprio contexto e, armados com o innerContext , podemos finalmente chamar Scaffold.of (innerContext)

Uma versão anotada do código acima segue

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

Eu não me incomodaria em usar a snackbar padrão, porque você pode importar um pacote flushbar, o que permite uma maior personalização:

https://pub.dev/packages/flushbar

Por exemplo:

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

Uma solução mais eficiente é dividir sua função de compilação em vários widgets. Isso introduz um 'novo contexto', do qual você pode obter o Andaime

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
fonte
0

Extraia o widget de botão que exibirá a lanchonete.

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
fonte
Como sua abordagem se compara às respostas existentes? Existe uma razão para escolher essa abordagem, digamos, a resposta aceita?
Jeremy Caney
0

aqui usamos um construtor para envolver outro widget onde precisamos da lanchonete

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