Scaffold.of () вызывается с контекстом, не содержащим Scaffold

Как видите, моя кнопка находится внутри тела Scaffold. Но у меня такое исключение:

Scaffold.of () вызывается с контекстом, не содержащим 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);
}

РЕДАКТИРОВАТЬ:

Нашел другое решение этой проблемы. Если мы дадим Scaffold ключ, который является GlobalKey<ScaffoldState>, мы можем отобразить SnackBar, как показано ниже, без необходимости заключать наше тело в виджет Builder. Однако виджет, который возвращает Scaffold, должен быть виджетом с отслеживанием состояния.

 _scaffoldKey.currentState.showSnackBar(snackbar); 

person Figen Güngör    schedule 12.07.2018    source источник
comment
Я нашел очень хорошее руководство, в котором он сделал оболочку, чтобы он мог легко изменять или контролировать контекст всего приложения: noobieprogrammer.blogspot.com/2020/06/   -  person doppelgunner    schedule 27.06.2020


Ответы (15)


Это исключение происходит из-за того, что вы используете context виджета, который создал экземпляр Scaffold. Не context ребенка Scaffold.

Вы можете решить эту проблему, просто используя другой контекст:

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

Обратите внимание, что хотя мы здесь используем Builder, это не единственный способ получить другой BuildContext.

Также возможно извлечь поддерево в другой Widget (обычно с помощью рефакторинга extract widget)

person Rémi Rousselet    schedule 12.07.2018
comment
Я получаю это исключение с этим: setState () или markNeedsBuild (), вызываемые во время сборки. I / flutter (21754): этот виджет Scaffold нельзя пометить как требующий сборки, потому что структура уже находится в процессе создания виджетов I / flutter (21754) :. - person Figen Güngör; 12.07.2018
comment
onPressed, который вы передали RaisedButton, не является функцией. Измените его на () => _displaySnackBar(context) - person Rémi Rousselet; 12.07.2018
comment
Попался: onPressed: () {_displaySnackBar (context);}, спасибо =) - person Figen Güngör; 12.07.2018
comment
Я думал, что с .of (context) он должен перемещаться вверх по иерархии виджетов, пока не встретит один из заданного типа, в данном случае Scaffold, с которым он должен столкнуться до того, как достигнет сборки Widget (... Это не работает таким образом ? - person CodeGrue; 04.08.2018
comment
@ RémiRousselet, сколько типов контекста есть во Flutter? Как вы сказали, контекст виджета, контекст дочернего элемента Scaffold. - person CopsOnRoad; 27.09.2018
comment
@CopsOnRoad Только один. Scaffold - это тоже виджет. - person Rémi Rousselet; 27.09.2018
comment
@CodeGrue, это действительно так, но context, передаваемый в Scaffold.of(), был выше Scaffold в иерархии, поскольку это был контекст, переданный HomePage.build() - person jbg; 15.01.2019
comment
код из вопроса такой же, как в официальных документах (flutter.dev/docs/cookbook/design/snackbars), который не использует Builder, это ошибка в официальной документации? - person temirbek; 14.03.2019
comment
Builder - не единственный способ получить другой BuildContext. - person Rémi Rousselet; 14.03.2019
comment
@ RémiRousselet Какие еще способы получить другой BuildContext? Я могу подумать об использовании GlobalKeys, но что еще? - person stevenspiel; 20.06.2019
comment
Разделите свой виджет на несколько. - person Rémi Rousselet; 20.06.2019
comment
Чтобы узнать об обновленном подходе, см. Этот ответ от @Deczaloth. Новый подход требует, чтобы ScaffoldMessenger отображал SnackBar. В этом случае Builder больше не требуется для предоставления новой области с BuildContext, которая находится «под» шаблоном. - person Keith DC; 03.06.2021

Вы можете использовать GlobalKey. Единственным недостатком является то, что использование GlobalKey может быть не самым эффективным способом сделать это.

Хорошо то, что вы также можете передать этот ключ другому классу настраиваемых виджетов, который не содержит никаких каркасов. См. (здесь)

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
  }
}
person Lebohang Mbele    schedule 21.12.2018
comment
Могу я спросить, как это воспроизвести, если ваше дерево состоит из нескольких виджетов, определенных в нескольких файлах? _scaffoldKey является частным из-за символа подчеркивания в начале. Но даже если бы это было не так, он находится внутри класса HomePage и поэтому недоступен без создания экземпляра новой HomePage где-нибудь в дереве, по-видимому, создавая циклическую ссылку. - person ExactaBox; 24.12.2019
comment
чтобы ответить на мой собственный вопрос: создайте одноэлементный класс со свойством GlobalKey<ScaffoldState>, отредактируйте конструктор виджета с помощью скаффолда, чтобы установить ключевое свойство синглтона, затем в дереве дочернего виджета мы можем вызвать что-то вроде mySingleton.instance.key.currentState.showSnackBar() ... - person ExactaBox; 24.12.2019
comment
Почему это неэффективно? - person user2233706; 07.03.2020
comment
@ user2233706 Это упоминается в документации GlobalKey здесь. Также этот пост может пролить больше света. - person Lebohang Mbele; 11.03.2020
comment
_scaffoldKey.currentState.showSnackBar(snackBar); был амортизирован. - person Pankaj; 11.01.2021
comment
Относительно устаревания: см. Этот ответ @Deczaloth. новый подход призывает ScaffoldMessenger показать SnackBar. - person Keith DC; 03.06.2021

Вы можете решить эту проблему двумя способами:

1) Использование виджета 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) Использование 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);
          },
        ),
     );
   }
}
person Sanjayrajsinh    schedule 10.10.2019
comment
Оба подхода были амортизированы. - person Pankaj; 11.01.2021
comment
@Pankaj. А у вас есть исходный код, который устарел? Класс виджета Builder является наиболее приемлемым ответом, а второй верхний ответ использует GlobalKey (который, как было отмечено, может быть дорогостоящим, а также является реактивным антипаттерном). И хотя этот ответ кажется излишним для двух верхних ответов, я не могу найти ссылку на то, что они устарели. Спасибо за любые идеи. - person Keith DC; 03.06.2021
comment
Что касается первого решения, я знаю, что Scaffold.of(context).showSnackBar(mySnackBar); был заменен на ScaffoldMessenger.of(context).showSnackBar(mySnackBar); . Это то, о чем вы говорили, когда решение 1 устарело? РЕДАКТИРОВАТЬ: О! Это почему? ... В этом случае от Builder больше не требуется предоставлять новую область с BuildContext, которая находится «под» шаблоном. - person Keith DC; 03.06.2021
comment
Неважно. Я нашел расширенный ответ @Deczaloth ниже. Я рад, что поймал твой комментарий. - person Keith DC; 03.06.2021

ОБНОВЛЕНИЕ - 2021

Scaffold.of(context) устарел в пользу ScaffoldMessenger.

Проверьте это в документации метода:

ScaffoldMessenger теперь обрабатывает SnackBars, чтобы они сохранялись на разных маршрутах и ​​всегда отображались в текущем Scaffold. По умолчанию корневой ScaffoldMessenger включен в MaterialApp, но вы можете создать свою собственную контролируемую область для ScaffoldMessenger, чтобы в дальнейшем контролировать, какие скаффолды получают ваши SnackBars.

@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: () {
              ScaffoldMessenger.of(context).showSnackBar(SnackBar(
                content: Text('Hello!'),
              ));
            },
          ),
        );
      },
    ),
  );
}

Подробную информацию об устаревании и новом подходе можно найти здесь:

person SANAT    schedule 01.05.2019
comment
подход были обесценены - person Pankaj; 11.01.2021
comment
Относительно устаревания: см. Этот ответ от @Deczaloth. Новый подход требует, чтобы ScaffoldMessenger отображал SnackBar. В этом случае Builder больше не требуется для предоставления новой области с BuildContext, которая находится «под» шаблоном. - person Keith DC; 03.06.2021

Простым способом решения этой проблемы будет создание ключа для вашего каркаса, подобного этому финальному, с помощью следующего кода:

Первый: GlobalKey<ScaffoldState>() _scaffoldKey = GlobalKey<ScaffoldState> ();

Второй: назначьте ключ к эшафоту key: _scaffoldKey

В-третьих: вызовите Snackbar, используя _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Welcome")));

person Mohamed Abdul-Malik    schedule 19.06.2019
comment
Оба подхода были амортизированы. - person Pankaj; 11.01.2021
comment
Относительно устаревания: см. Этот ответ @Deczaloth. Новый подход требует, чтобы ScaffoldMessenger отображал SnackBar. - person Keith DC; 03.06.2021

ОБНОВЛЕНИЕ 03.03.2021: Только что вышел Flutter 2.0 ...

(Что касается исторической ценности, я также приведу ниже свой исходный ответ ...)


Теперь ... Enter ...

ScaffoldMessenger

Из документа мы читаем

SnackBar API в Scaffold теперь обрабатывается ScaffoldMessenger, один из которых доступен по умолчанию в контексте MaterialApp.

Итак, используя новый ScaffoldMessenger, теперь вы сможете писать такой код, как

Scaffold(
  key: scaffoldKey,
  body: GestureDetector(
    onTap: () {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(
        content: const Text('snack'),
        duration: const Duration(seconds: 1),
        action: SnackBarAction(
          label: 'ACTION',
          onPressed: () { },
        ),
      ));
    },
    child: const Text('SHOW SNACK'),
  ),
);

Теперь снова в документе мы видим, что

При представлении SnackBar во время перехода SnackBar завершит анимацию Hero, плавно переходя на следующую страницу.

ScaffoldMessenger создает область, в которой все дочерние скаффолды регистрируются для получения SnackBars, и именно так они сохраняются при этих переходах. При использовании корневого ScaffoldMessenger, предоставляемого MaterialApp, все дочерние скаффолды получают SnackBars, если только дальше по дереву не создается новая область ScaffoldMessenger. Создавая собственный ScaffoldMessenger, вы можете контролировать, какие скаффолды получают SnackBars, а какие нет, в зависимости от контекста вашего приложения.


ОРИГИНАЛЬНЫЙ ОТВЕТ

Само поведение, с которым вы столкнулись, даже упоминается как сложный случай в Flutter документация.

Как исправить

Проблема устраняется по-разному, как вы можете видеть из других ответов, размещенных здесь. Например, часть документации, на которую я ссылаюсь, решает проблему с помощью Builder, который создает

внутренний BuildContext, так что onPressed методы могут ссылаться на Scaffold с помощью Scaffold.of().

Таким образом, вызов showSnackBar из 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.')
            ));
          }
        );
      }
    )
  );
}

Теперь немного подробностей для любознательного читателя.

Я сам нашел весьма поучительным изучить документацию Flutter, просто установив (Android Studio) курсор на фрагмент кода (Flutter класс, метод и т. д.) и нажмите ctrl + B, чтобы отобразить документацию для этого конкретного элемента.

Конкретная проблема, с которой вы столкнулись, упоминается в документе для BuildContext, где можно прочитать

Каждый виджет имеет свой собственный BuildContext, который становится родительским для виджета, возвращаемого функцией [...]. Build.

Итак, это означает, что в нашем случае context будет родительским элементом нашего виджета Scaffold при его создании (!). Кроме того, в документе Scaffold.of говорится, что он возвращает

Состояние из ближайшего экземпляра [Scaffold] этого класса, который включает данный контекст.

Но в нашем случае context не включает (пока) Scaffold (он еще не построен). Здесь в дело вступает Builder!

И снова документ освещает нас. Там мы можем прочитать

[Класс Builder - это просто] Платонический виджет, который вызывает закрытие для получения своего дочернего виджета.

Эй, погоди, что ?! Хорошо, я признаю: это не очень помогает ... Но достаточно сказать (после другой поток SO), который

Назначение класса Builder - просто создавать и возвращать дочерние виджеты.

Итак, теперь все становится ясно! Вызывая Builder внутри Scaffold, мы создаем Scaffold, чтобы иметь возможность получить его собственный контекст, и, вооружившись этим innerContext, мы, наконец, можем вызовите Scaffold.of (innerContext)

Аннотированная версия приведенного выше кода следует

@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.')
            ));
          }
        );
      }
    )
  );
}
person Deczaloth    schedule 09.05.2020

Используйте ScaffoldMessenger (рекомендуется)

var snackBar = SnackBar(content: Text('Hi there'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);

Пример (без Builder или GlobalKey)

Scaffold(
  body: ElevatedButton(
    onPressed: () {
      var snackBar = SnackBar(content: Text('Hello World'));
      ScaffoldMessenger.of(context).showSnackBar(snackBar);
    },
    child: Text('Show SnackBar'),
  ),
)
person CopsOnRoad    schedule 09.12.2018
comment
@Pankaj, я также получал ошибку после обновления флаттера, но после очистки флаттера, запуска флаттера, переустановки плагинов для моего vscode он снова начал работать, в настоящее время я нахожусь в бета-канале - person Mrudul Addipalli; 30.01.2021
comment
Это решило аналогичную проблему, которая была у меня. Спасибо - person LakeRunner; 05.02.2021

Начиная с Flutter версии 1.23-18.1.pre, вы можете использовать ScaffoldMessenger.

final mainScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();

class Main extends StatelessWidget {
  @override
  Widget build(BuildContext) {
    return MaterialApp(
      ...
      scaffoldMessengerKey: mainScaffoldMessengerKey
      ...
    );
  }
}

Где-то внутри приложения:

mainScaffoldMessengerKey.currentState.showSnackBar(Snackbar(...));
person BambinoUA    schedule 27.10.2020

Более эффективное решение - разделить вашу функцию сборки на несколько виджетов. Это вводит «новый контекст», из которого вы можете получить 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'),
              ),
            );
          }),
    );
  }
}
person TDM    schedule 17.04.2020
comment
Это должен быть принятый ответ, поскольку официальная документация рекомендует этот подход в качестве первого выбора. - person Igor Soloydenko; 24.01.2021

Я бы не стал использовать закусочную по умолчанию, потому что вы можете импортировать пакет флешбара, который обеспечивает большую настраиваемость:

https://pub.dev/packages/flushbar

Например:

Flushbar(
                  title:  "Hey Ninja",
                  message:  "Lorem Ipsum is simply dummy text of the printing and typesetting industry",
                  duration:  Duration(seconds: 3),              
                )..show(context);
person Alex    schedule 04.06.2020
comment
Теперь он помечен как ПРЕКРАЩЕННОЕ - person Kévin; 17.05.2021

Я могу опоздать. Но и это кому-то поможет. Добавьте _key под каркасом. Затем используйте этот _key для вызова метода openDrawer.

return Scaffold(
  key: _scaffoldKey, //this is the key
  endDrawer: Drawer(),
  appBar: AppBar( 
//all codes for appbar here
actions: [
IconButton(
        splashRadius: 20,
        icon: Icon(Icons.settings),
        onPressed: () {
          _scaffoldKey.currentState.openEndDrawer(); // this is it
       
        },
      ),]
person ABHIMANGAL MS    schedule 30.12.2020

Извлеките виджет кнопки, который будет отображать снэк-бар.

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'),
                  );
  }
}
person Rashid    schedule 02.06.2020
comment
Как ваш подход соотносится с существующими ответами? Есть ли причина предпочесть этот подход, скажем, принятому ответу? - person Jeremy Caney; 03.06.2020

здесь мы используем конструктор, чтобы обернуть другой виджет, где нам нужна закусочная

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

Может, в этом коде есть решение.

ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("SnackBar Message")));
person Lef    schedule 02.05.2021

Попробуйте этот код:

Singleton.showInSnackBar(
    Scaffold.of(context).context, "Theme Changed Successfully");
// Just use Scaffold.of(context) before context!!
person Sanket Jadhav    schedule 19.09.2020