Flutter i snackbar. 3 sposoby na rozwiązanie problemu z BuildContext
Flutter. W tym artykule dowiesz się w jaki sposób poradzić sobie z wyjątkiem "Scaffold.of() called with a context that does not contain a Scaffold."
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.
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.