What does MaterialPageRoute do with context?
And what is the purpose of builder: here.
MaterialPageRoute(builder: (context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Route"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
),
);
}
Every state of widget required some context to perform some work, direct or indirect context should be used.
The [BuildContext] for a particular widget can change location over time as the widget is moved around the tree. Because of this, values returned from
the methods in this class should not be cached beyond the execution of a
single synchronous function.
Example:
In the question code for back navigation used Navigator.pop(context); so it will be running in a separate state, not the build(contex) from where navigation starts
Related
I am working on the iPad and I have set up the screen to be SplitScreens. Thus I have two Navigators side by side. However, when I push a new Screen from the second Navigator I get a weird animation where the second screen (right) overlaps the first screen (left).
Please look at this video that explains it very good:
https://imgur.com/a/93UmrGm
SplitView(
menu: Navigator(
key: firstSplitNavigatorKey,
onGenerateRoute: (routeSettings) {
return MaterialPageRoute(
builder: (context) =>
const KursplanungScreen(title: 'Demo App'),
);
},
),
content: Navigator(
key: secondSplitNavigatorKey,
onGenerateRoute: (routeSettings) {
return MaterialPageRoute(
builder: (context) => const Scaffold(
body: Center(
child: Text(
"Herzlich Willkommen!"))),
);
},
),
menuWidth: 300,
),
Then somewhere in the second screen i call this:
TextButton(
onPressed: () => secondSplitNavigatorKey.currentState!.push(
MaterialPageRoute(
builder: (_) => const CourseOverviewScreen(),
),
),
child: const Text("Bearbeiten"),
),
How can I avoid that third weird animation?
You need to use a Navigator object to handle the right side pages.Use a Navigator object as widget father of the right side and handle the stack of pages to navigate only in this side. The default Navigator will change the entire page and will create a whole new one.
The left side, don't need to have a Navigator, only handle it using Widgets states(StatefullWidget or something like that).
SplitView(
menu: MenuSideBar(
onChangeSection: (Widget section) {
setState((){
pages.last=section;
});
}),
content: Navigator(
pages: pages,
),
),
I have a DetailPage that must return something to the code that called it. The documentation Return data from a screen explains that you must call Navigator.pop(context, whateverYouMustReturn) on some button.
So far so good, but what happens if the user clicks on the back button on the AppBar instead?? How do I return something then??
PS I'm using Navigator 1.0, btw
Provide your own leading widget and override the callback.
AppBar(
leading: BackButton(
onPressed: () {
Navigator.of(context).pop(myData);
},
),
),
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: Container(), //Make leading an empty container to hide the default back button
),
body: WillPopScope(
onWillPop: () async {
Navigator.pop(context); //This method returns future boolean, based on your condition you can either
return true; //allow you to go back or you can show a toast and restrict in this page
},
child: Container(), //Your details page widget instead of container
),
);
}
I want to create a transition immediately from one screen to another (no animation). However isInitialRoute generates an error due to an update to the Router widget. The suggestion is to use onGenerateInitialRoutes but I am not sure how to implement it.
The flutter design doc recommends this:
Routes & RouteSettings
Currently, the only way to add a Route to the history stack without playing its entrance animation is to mark it as an initial route in its RouteSettings. The declarative API requires that routes can be added without animation at any time. This is useful when the route is covered by another route and playing the animation simply doesn't make sense. To support this, a didAdd method is added to the Route interface. This method is called by the Navigator instead of didPush when the Route should just be added without its regular entrance animation. To simplify things, this new method will also be used to bring the initial route on screen. This makes the RouteSettings.initialRoute parameter useless and it will be removed from RouteSettings. This is a minor breaking change.
This is the code that is generating the error on isInitialRoute:
import 'package:flutter/cupertino.dart'
show
CupertinoApp,
CupertinoButton,
CupertinoPageRoute,
CupertinoPageScaffold;
import 'package:flutter/widgets.dart'
show
BuildContext,
Center,
Column,
Navigator,
Route,
RouteSettings,
SafeArea,
Spacer,
Text,
runApp,
Widget;
Widget makeButton(BuildContext context, String routeName) =>
new CupertinoButton(
onPressed: () => Navigator.pushReplacementNamed(context, routeName),
child: Text('Go to \'$routeName\''),
);
Route generateRoute(RouteSettings settings) {
switch (settings.name) {
case 'not-animated':
return new CupertinoPageRoute(
settings: RouteSettings(name: settings.name, isInitialRoute: true),
builder: (context) => CupertinoPageScaffold(
child: SafeArea(
child: Center(
child: Column(
children: [
Spacer(),
Text('This is \'not-animated\''),
makeButton(context, 'animated'),
Spacer(),
],
),
),
),
),
);
default:
return null;
}
}
void main() {
runApp(
CupertinoApp(
onGenerateRoute: generateRoute,
initialRoute: 'animated',
routes: {
'animated': (context) => CupertinoPageScaffold(
child: SafeArea(
child: Center(
child: Column(
children: [
Spacer(),
Text('This is \'animated\''),
makeButton(context, 'not-animated'),
Spacer(),
],
),
),
),
),
},
),
);
}
If You Want to just navigate your screen add this to your code
onTap: (){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => yoursecondscreenname()),
);
},
When a user clicks on delete for an employee, an AlertDialog shall pop up to warn the user.
If the user confirms the deletion, then the AlertDialog disappears and at the bottom of the Scaffold a SnackBar should appear with an Undo function.
Problem:
When I implement the SnackBar method showSnackBar(context, index, employee) within the AlertDialog class I get the following error:
he following assertion was thrown while handling a gesture:
Scaffold.of() called with a context that does not contain a Scaffold.
showDeleteDialog(BuildContext context, Employee employee, int index) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title:
Text('Are you sure you want to delete: ${employee.name} ?'),
actions: <Widget>[
Row(
children: <Widget>[
FlatButton(
child: Text('Yes'),
onPressed: () {
DatabaseProvider.db.deleteEmployee(employee.id).then(
(_) => BlocProvider.of<EmployeeBloc>(context)
.add(DeleteEmployee(index)));
Navigator.pop(context,employee);
showSnackBar(context, index, employee);
}),
FlatButton(
child: Text('No!'),
onPressed: () => Navigator.pop(context)),
],
)
],
));
}
Instead, I thought I could return an employee from the showDeleteDialog when I confirm the deletion. When the result is not null, then I should show the SnackBar. I tried to implement this with Future/Async but with no success.
onPressed: () async {
Employee deletedEmployee = await showDeleteDialog(context, employee, index);
await showSnackBar(context, index, deletedEmployee);
},
Edit: I would like to avoid using GlobalKey if possible, since I read it is not good for the App's performance.
As the error says Scaffold.of() called with a context that does not contain a Scaffold., that means the current context that you are passing to showSnackBar() method doesn't contain a Scaffold in the immediate parent.
We can fix this by using GlobalKey and assign it to the Scaffold. Declare a global key in your stateful widget and pass this as a key in your Scaffold, as below:
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
....
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text(widget.title),
),
I call a method _showSnackBar() after navigator.pop() on OK button click inside the alertDialog, as below:
return AlertDialog(
title: Text('Not in stock'),
content: const Text('This item is no longer available'),
actions: <Widget>[
FlatButton(
child: Text('Ok'),
onPressed: () {
Navigator.of(context).pop();
_showSnackBar();
},
),
],
);
Then in the _showSnackBar() method, use the key to show the snackbar, as below:
void _showSnackBar() {
_scaffoldKey.currentState.showSnackBar(
SnackBar(
content: Text('Snackbar is displayed'),
));
}
With this approach, once you tap on OK button on alertDialog, the dialog will close and you'll see the snackbar. You may need to customize this per your code as you shared above.
Hope this answers your question and resolves your issue.
Found the solution and it is super easy...
I only had to re-name one of the context to a dialogContext
showDeleteDialog(BuildContext context, Employee employee, int index) {
showDialog(
context: context,
builder: (dialogContext) => AlertDialog(
title:
Text('Are you sure you want to delete: ${employee.name} ?'),
actions: <Widget>[
Row(
children: <Widget>[
FlatButton(
child: Text('Yes'),
onPressed: () {
DatabaseProvider.db.deleteEmployee(employee.id).then(
(_) => BlocProvider.of<EmployeeBloc>(dialogContext)
.add(DeleteEmployee(index)));
Navigator.pop(dialogContext);
showSnackBar(context, index, employee);
}),
FlatButton(
child: Text('No!'),
onPressed: () => Navigator.pop(context)),
],
)
],
));
}
sending data between screen using Provider with Navigator concept make a conflict
error after the run
The following ProviderNotFoundError was thrown building SecondRoute(dirty):
Error: Could not find the correct Provider above this SecondRoute Widget
To fix, please:
Ensure the Provider is an ancestor to this SecondRoute Widget
Provide types to Provider
Provide types to Consumer
Provide types to Provider.of()
Always use package imports. Ex: `import 'package:my_app/my_code.dart';
Ensure the correct context is being used.
https://www.ideone.com/xHXK5m
you can pass data like this put data in class
RaisedButton(
child: Text(‘Send data to the second page’),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondPage(
data: data,
)),
);
},
),
and recieve data like this
class SecondPage extends StatelessWidget {
final Data data;
SecondPage({this.data});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(‘Constructor — second page’),
),
body: Container(
padding: EdgeInsets.all(12.0),
alignment: Alignment.center,
child: Column(
children: <Widget>[
Container(
height: 54.0,
padding: EdgeInsets.all(12.0),
child: Text(‘Data passed to this page:’,
style: TextStyle(fontWeight: FontWeight.w700))),
Text(‘Text: ${data.text}’),
Text(‘Counter: ${data.counter}’),
Text(‘Date: ${data.dateTime}’),
],
),
),
);
}
You can try placing whatever Provider value you have above the MaterialApp and thus the navigation or, when you push to a second page, provide the value again. Providers are scoped to widget trees, so this is expected behavior.
As an example, to pass a value to another route, you could do something like
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
final foo = 'foo';
Provider<String>.value(
value: foo,
child: NewPage(),
);
}));
},
And then just consume it like regular in the NewPage route.
Try this code:
MaterialApp(
title: 'Navigation Basics',
home: ChangeNotifierProvider(
builder: (context) => Data(),
child: FirstRoute(),
))
Delete ChangeNotifierProvider in FirstRoute