I´m new in flutter and i´m trying to create a navigation to another page called Registro();
I'm trying with this solutionthat i found in a post here in stackoverflow.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(home: Login());
}
}
class Login extends StatelessWidget {
const Login({Key? key}) : super(key: key);
static const String _title = 'Sample App';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
bottomNavigationBar: BottomAppBar(
elevation: 3,
child: Row(
children: <Widget>[
const Text('¿No tienes una cuenta?'),
TextButton(
child: const Text(
'Registrate',
style: TextStyle(fontSize: 20),
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => new Registro()),
);
},
)
],
mainAxisAlignment: MainAxisAlignment.center,
),
),
),
);
}
}
Also i tried this solution -> and doesnt works :c
Navigator operation requested with a context that does not include a Navigator
If you want to use Navigator.push(context, yourPageRoute), (which does a Navigator.of(context)), context needs to be able to access a Navigator placed above in the widget tree.
Usually, this Navigator is the root one that is being built in MaterialApp.
I your case you have
Widget build(BuildContext context) {
return MaterialApp( // <- The Navigator is inside this widget.
// ...
TextButton(
onPressed: () {
Navigator.of(context);
// ...
},
),
);
}
But the context you are using comes from the build method of MyApp, which is not below the MaterialApp widget you are using.
So you need to use the context that comes from below the MaterialApp in the widget tree.
How to solve it
You could use a Builder widget to be able to access the context from its builder method:
Widget build(BuildContext context) {
return MaterialApp( // <- The Navigator is inside this widget.
// ...
Builder(
builder: (context) {
return TextButton(
onPressed: () {
Navigator.of(context); // <- Now the context comes from Builder which is below MaterialApp and you should be able to access its Navigator.
// ...
},
),
},
),
);
}
What codes should I use to go back to the page with the flutter android back button? I looked inside youtube, especially on Stackoverflow, but I couldn't get any results.
You can use WillPopScope
Here is example code:
class MyPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
// This function will handle back button
// When true, it's can back to previous page
// when false, back button will do nothing
return true;
},
child: Scaffold(
appBar: AppBar(
title: const Text('Flutter WillPopScope demo'),
),
body: Center(
child: Text('Hello world')
),
),
);
}
}
class Page2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new WillPopScope(
child: new Scaffold(
appBar: new AppBar(
title: new Text('Page 2'),
),
body: new Center(
child: new Text('PAGE 2'),
),
),
onWillPop: () async {
return false;
},
);
}
}
Future<T> pushPage<T>(BuildContext context, Widget page) {
return Navigator.of(context)
.push<T>(MaterialPageRoute(builder: (context) => page));
}
Can call the page like:
pushPage(context, Page2());
I am trying to build a demo chat app with Flutter. After my main screen, I am using Navigator.push to go to the details screen.
Screenshot of problem:
build method of 1st screen:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Chat Thread App"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.settings),
onPressed: () {
Navigator.pushNamed(context, '/settings');
},
)
],
),
body: isLoading
? Center(
child: CircularProgressIndicator(),
)
: new ChatThreadListCard(messageThreads: _messageThreadLists, user: _user,),
);
}
code of Navigator.push method:
Navigator.push(context, MaterialPageRoute(
builder: (context) => ChatDetailsScreen(threadModel: new ThreadModel(
user.id,
user.fullName,
user.pic,
"otherId",
"otherName",
"otherPic",
post.threadId
)
),
),);
build method of 2nd screen, where the problem is produced:
return Scaffold(
appBar: AppBar(
title: Text("Chat demo"),
),
body: WillPopScope(
child: isLoading
? Center(
child: CircularProgressIndicator(),
)
: Stack(
alignment: AlignmentDirectional.bottomCenter,
children: <Widget>[
SizedBox(
width: 300,
height: 300,
),
Column(
children: <Widget>[
buildChat(),
buildInput(),
],
)
],
),
onWillPop: onBackPress,
),
);
the problem turns out to be, i was creating a MaterialApp widget in scaffold's body. so, when the onTap method was called, the new screen was replaced insdie the MaterialApp's area. didnt replace the whole screen.
the trick was to remove the return new MaterialApp().
thanks everyone.
I'm guessing something isn't working right with where you're setting up the Material App?
app.dart:
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage());
}
}
home_page and second_page
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
#override
State createState() => HomePageState();
}
class HomePageState extends State<HomePage> with TickerProviderStateMixin {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Page'),
),
body: Container(
child: Center(child: RaisedButton(child: Text('Forward'), onPressed: () async {
await Navigator.push(context, MaterialPageRoute(builder: (context) => SecondPage()));
},)),
));
}
}
class SecondPage extends StatefulWidget {
#override
State createState() => SecondPageState();
}
class SecondPageState extends State<SecondPage> with TickerProviderStateMixin {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Second Page'),
),
body: Container(
child: Center(child: RaisedButton(child: Text('Backward'), onPressed: () {
Navigator.of(context).pop();
},)),
));
}
}
Which produces:
As you can see, my button is inside the Scaffold's body. But I get this exception:
Scaffold.of() called with a context that does not contain a 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);
}
EDIT:
I found another solution to this problem. If we give the Scaffold a key which is the GlobalKey<ScaffoldState>, we can display the SnackBar as following without the need to wrap our body within the Builder widget. The widget which returns the Scaffold should be a Stateful widget though.
_scaffoldKey.currentState.showSnackBar(snackbar);
This exception happens because you are using the context of the widget that instantiated Scaffold. Not the context of a child of Scaffold.
You can solve this by just using a different context :
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'),
),
),
),
);
Note that while we're using Builder here, this is not the only way to obtain a different BuildContext.
It is also possible to extract the subtree into a different Widget (usually using extract widget refactor)
You can use a GlobalKey. The only downside is that using GlobalKey might not be the most efficient way of doing this.
A good thing about this is that you can also pass this key to other custom widgets class that do not contain any scaffold. See(here)
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
}
}
You can solve this problem in two ways:
1) Using Builder widget
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) Using 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);
},
),
);
}
}
UPDATE - 2021
Scaffold.of(context) is deprecated in favor of ScaffoldMessenger.
Check this from the documentation of method:
The ScaffoldMessenger now handles SnackBars in order to persist across
routes and always be displayed on the current Scaffold. By default, a
root ScaffoldMessenger is included in the MaterialApp, but you can
create your own controlled scope for the ScaffoldMessenger to further
control which Scaffolds receive your 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!'),
));
},
),
);
},
),
);
}
You can check the detailed deprecation and new approach here:
Simple way to solving this issue will be creating a key for your scaffold like this final with the following code:
First: GlobalKey<ScaffoldState>() _scaffoldKey = GlobalKey<ScaffoldState>
();
Scecond: Assign the Key to your Scaffold key: _scaffoldKey
Third: Call the Snackbar using
_scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Welcome")));
UPDATE: recommended approach by Flutter (as of 20.12.2022)...
To show a Snackbar you should be using:
ScaffoldMessenger
From the docu we read
The SnackBar API within the Scaffold is now handled by the ScaffoldMessenger, one of which is available by default within the context of a MaterialApp
So, with ScaffoldMessenger now you will be able to write code like
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'),
),
);
Now, again in the docu we can see that
When presenting a SnackBar during a transition, the SnackBar will complete a Hero animation, moving smoothly to the next page.
The ScaffoldMessenger creates a scope in which all descendant Scaffolds register to receive SnackBars, which is how they persist across these transitions. When using the root ScaffoldMessenger provided by the MaterialApp, all descendant Scaffolds receive SnackBars, unless a new ScaffoldMessenger scope is created further down the tree. By instantiating your own ScaffoldMessenger, you can control which Scaffolds receive SnackBars, and which do not based on the context of your application.
ORIGINAL ANSWER
The very behavior you are experiencing is even referred to as a "tricky case" in the Flutter documentation.
How To Fix
The issue is fixed in different ways as you can see from other answers posted here. For instance, the piece of documentation i refer to solves the issue by using a Builder which creates
an inner BuildContext so that the onPressed methods can refer to the Scaffold with Scaffold.of().
Thus a way to call showSnackBar from Scaffold would be
#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.')
));
}
);
}
)
);
}
Now some detail for the curious reader
I myself found quite instructive to explore the Flutter documentation by simply (Android Studio) setting the cursor on a piece of code (Flutter class, method, etc.) and pressing ctrl+B to be shown the documentation for that specific piece.
The particular problem you are facing is mentioned in the docu for BuildContext, where can be read
Each widget has its own BuildContext, which becomes the parent of the widget returned by the [...].build function.
So, this means that in our case context will be the parent of our Scaffold widget when it is created (!). Further, the docu for Scaffold.of says that it returns
The state from the closest [Scaffold] instance of this class that encloses the given context.
But in our case, context does not encloses (yet) a Scaffold (it has not yet been built). There is where Builder comes into action!
Once again, the docu illuminates us. There we can read
[The Builder class, is simply] A platonic widget that calls a closure to obtain its child widget.
Hey, wait a moment, what!? Ok, i admit: that is not helping a lot... But it is enough to say (following another SO thread) that
The purpose of the Builder class is simply to build and return child widgets.
So now it all becomes clear! By calling Builder inside Scaffold we are building the Scaffold in order to be able to obtain its own context, and armed with that innerContext we can finally call Scaffold.of(innerContext)
An annotated version of the code above follows
#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.')
));
}
);
}
)
);
}
Use ScaffoldMessenger (Recommended)
var snackBar = SnackBar(content: Text('Hi there'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
Example (Without Builder or GlobalKey)
Scaffold(
body: ElevatedButton(
onPressed: () {
var snackBar = SnackBar(content: Text('Hello World'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
},
child: Text('Show SnackBar'),
),
)
From Flutter version 1.23-18.1.pre you can use ScaffoldMessenger
final mainScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
class Main extends StatelessWidget {
#override
Widget build(BuildContext) {
return MaterialApp(
...
scaffoldMessengerKey: mainScaffoldMessengerKey
...
);
}
}
Somewhere inside app:
mainScaffoldMessengerKey.currentState.showSnackBar(Snackbar(...));
A more efficient solution is to split your build function into several widgets.
This introduce a 'new context', from which you can obtain 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'),
),
);
}),
);
}
}
I may be late. But this will help someone too.
Add a _key under the Scaffold.
Then use that _key to call the openDrawer method.
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
},
),]
I wouldn't bother using the default snackbar, because you can import a flushbar package, which enables greater customizability:
https://pub.dev/packages/flushbar
For example:
Flushbar(
title: "Hey Ninja",
message: "Lorem Ipsum is simply dummy text of the printing and typesetting industry",
duration: Duration(seconds: 3),
)..show(context);
Maybe there is a solution in this code.
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("SnackBar Message")));
Expanded(
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Builder(
builder: (context) => RaisedButton(
onPressed: () {
final snackBar = SnackBar(
content: Text("added to cart"),
action: SnackBarAction(
label: 'Undo',
onPressed: () {
// Some code to undo the change.
},
),
);
},
textColor: Colors.white,
color: Colors.pinkAccent,
child: Text("Add To Cart",
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.w600)),
),
),
),
)
here we use a builder to wrap in another widget where we need snackbar
Builder(builder: (context) => GestureDetector(
onTap: () {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('Your Services have been successfully created Snackbar'),
));
},
child: Container(...)))
Try this code:
Singleton.showInSnackBar(
Scaffold.of(context).context, "Theme Changed Successfully");
// Just use Scaffold.of(context) before context!!
If you create an Scafold there is an option for drawer. If you now create this drawer you get automaticly the menu icon on the leading position of the appbar. But i want an other icon there which opens the drawer. I tried to make an iconbutton myself on the leading position but this button can‘t open the drawer even with „Scafold.of(context).openDrawer()“ it can‘t open it.
Is there any option to replace the icon for the drawer button?
Use a Key in your Scaffold and show the drawer by calling myKey.currentState.openDrawer(), here is a working code:
import "package:flutter/material.dart";
class Test extends StatefulWidget {
#override
_TestState createState() => new _TestState();
}
class _TestState extends State<Test> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
return new Scaffold(
key: _scaffoldKey,
drawer: new Drawer(),
appBar: new AppBar(
leading: new IconButton(
icon: new Icon(Icons.settings),
onPressed: () => _scaffoldKey.currentState.openDrawer(),
),
),
);
}
}
Alternative to the accepted answer which does not require a GlobalKey:
class _TestState extends State<Test> {
#override
Widget build(BuildContext context) {
return new Scaffold(
drawer: new Drawer(),
appBar: new AppBar(
leading: Builder(
builder: (context) => IconButton(
icon: new Icon(Icons.settings),
onPressed: () => Scaffold.of(context).openDrawer(),
),
),
),
);
}
}
Using GlobalKey:
final GlobalKey<ScaffoldState> _key = GlobalKey(); // Create a key
#override
Widget build(BuildContext context) {
return Scaffold(
key: _key, // Assign the key to Scaffold.
drawer: Drawer(),
floatingActionButton: FloatingActionButton(
onPressed: () => _key.currentState!.openDrawer(), // <-- Opens drawer
),
);
}
Using Builder:
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: Drawer(),
floatingActionButton: Builder(builder: (context) {
return FloatingActionButton(
onPressed: () => Scaffold.of(context).openDrawer(), // <-- Opens drawer.
);
}),
);
}
you need initialize scaffoldKey
after that,
Open drawer and close drawer
GestureDetector(
onTap: () {
if(scaffoldKey.currentState.isDrawerOpen){
scaffoldKey.currentState.openEndDrawer();
}else{
scaffoldKey.currentState.openDrawer();
}
},
child: LeadingIcon(icon: Icons.menu),//your button
),