Flutter i snackbar. 3 sposoby na rozwiązanie problemu z BuildContext

Tworzysz aplikację Flutter. Co zrobić, gdy podczas próby wyświetlenia snackbara otrzymujemy następujący exception?

Scaffold.of() called with a context that does not contain a Scaffold.

Gdy po raz pierwszy natknąłem się na ten problem spędziłem kilka konkretnych godzin zanim świadomie zrozumiałem mój błąd. A wiec kiedy się on pojawia? Zgodnie z opisem wyjątku wtedy gdy próbujemy uzyskać instancję Scaffold na podstawie contextu, który go nie zawiera. Dzieje się tak, gdy wykorzystywany kontekst pochodzi z tego samego widgetu StatefulWidget, co ten, którego funkcja kompilacji tworzy widget Scaffold.

Proste, ale namierzenie miejsca występowania problemu za pierwszym razem może nastręczyć nie lada trudności.

To jest ciekawe!

Dzieje się tak, gdy wykorzystywany kontekst pochodzi z tego samego widgetu StatefulWidget, co ten, którego funkcja kompilacji tworzy widget Scaffold.

Flutter i snackbar. Przykładowy kod

Przeanalizuj poniższy kod. Jeśli spróbowałbyś wcisnąć przycisk, który według opisu powinien wyświetlić snackbara, uzyskasz omawiany wyjątek.

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Demo"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Click below to show a snackbar!',
            ),
            OutlineButton(
              child: Text("Click!"),
              onPressed: () {
                final snackBar = SnackBar(content: Text('My SnackBar!'));

                Scaffold.of(context).showSnackBar(snackBar);
              },
            )
          ],
        ),
      ),
    );
  }
}

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Demo',
      home: MyHomePage(),
    );
  }
}

W czym problem? Przecież widok jest zawarty w obiekcie Scaffold, więc wszystko powinno być ok. To prawda, o ile w danym momencie on już istnieje.

No dobrze, problem polega na tym, że w momencie wywołania Scaffold.of(context), Flutter nie zbudował jeszcze w pełni drzewa widoku. To dlatego pomimo umieszczenia jako rodzica właśnie obiektu Scaffold, otrzymujemy exception podczas próby pokazania snackbara.

W takim razie jak to naprawić?

Znam 3 sposoby na poprawę tego problemu. Wybór rozwiązania należy dla Ciebie.

1. Skorzystanie z widgetu Builder.

Zanim jego funkcja przypisana do builder zostanie wywołana jego rodzice muszą zostać zbudowani. Dzięki czemu wywołanie Scaffold.of(context), będzie w stanie wyszukać Scaffold w drzewie widgetów, ponieważ został już istnieje. To pozwoli na poprawne wyświetlenie snackbara. Parametr w currentContext równie dobrze mógłby nazywać się po prostu context.

lass MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Demo"),
      ),
      body: Builder(
        builder: (currentContext) => Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                'Click below to show a snackbar!',
              ),
              OutlineButton(
                child: Text("Click!"),
                onPressed: () {
                  final snackBar = SnackBar(content: Text('My SnackBar!'));

                  Scaffold.of(currentContext).showSnackBar(snackBar);
                },
              )
            ],
          ),
        ),
      ),
    );
  }
}

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Demo',
      home: MyHomePage(),
    );
  }
}
2. Nowy widget

Problem zostanie rozwiązany podobnie jak wcześniej. W momencie wywołania snackbara skorzystamy z nowego contextu, który będzie w stanie odnaleźć instancję Scaffold.

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Demo"),
        ),
        body: MyContent());
  }
}

class MyContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
        child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Text(
          'Click below to show a snackbar!',
        ),
        OutlineButton(
          child: Text("Click!"),
          onPressed: () {
            final snackBar = SnackBar(content: Text('My SnackBar!'));

            Scaffold.of(context).showSnackBar(snackBar);
          },
        )
      ],
    ));
  }
}

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Demo',
      home: MyHomePage(),
    );
  }
}
3. Skorzystanie z GlobalKey

Moim zdaniem najlepsze rozwiązanie. W tym rozwiązaniu odwołujemy się już bezpośrednio do obiektu Scaffold, dlatego samo wywołanie snackbara jest inne niż poprzednio.

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  var scaffoldKey = GlobalKey<ScaffoldState>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: scaffoldKey,
      appBar: AppBar(
        title: Text("Demo"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Click below to show a snackbar!',
            ),
            OutlineButton(
              child: Text("Click!"),
              onPressed: () {
                final snackBar = SnackBar(content: Text('My SnackBar!'));

                scaffoldKey.currentState.showSnackBar(snackBar);
              },
            )
          ],
        ),
      ),
    );
  }
}

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Demo',
      home: MyHomePage(),
    );
  }
}

Podsumowanie

Mam nadzieję, że wyjaśniłem Ci, co jest przyczyną problemu, a także to, w jaki sposób sobie z nim poradzić. Przedstawiłem Ci trzy rozwiązania, które możesz użyć w swojej aplikacji Flutter, a Ty wybierz te, które najlepiej sprawdzi się w konkretnym przypadku.


Newsletter

Zapisz się do mojego newslettera, aby nie przegapić nowych postów. Dodatkowo wyślę Ci darmowego ebooka mojego autorstwa zawierającego mnóstwo wskazówek dla programisty aplikacji mobilnych.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Wypełnij to pole
Wypełnij to pole
Proszę wprowadzić prawidłowy adres email.
You need to agree with the terms to proceed

Menu