When pushing on a new screen and wanting to disable the ability to swipe backwards I found the WillPopScope widget.
WillPopScope(
onWillPop: () async => false,
child: <some child>
however, it prevents the swipe gesture regardless of whether or not I return true of false.
Is it possible to tell this widget that it can pop back on certain screen states?
So effectively I would have:
WillPopScope(
onWillPop: () async => widget._canSwipeBack,
child: <some child>
Right now I have add/not-add the widget based on the screen state which seems quite odd.
Please refer to the below code
Without wrapping the widget with WillPopScope usually its performs Navigator.pop(context);
// disables swiping back
WillPopScope(
// disables swiping back or navigating back
onWillPop: () {},
child: Scaffold(
body: Container(),
),
);
WillPopScope(
onWillPop: () {
// whenever you want to navigate back to specific route
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
},
child: Scaffold(
body: Container(),
),
);
WillPopScope(
onWillPop: () {
// pop back
Navigator.pop(context);
},
child: Scaffold(
body: Container(),
),
);
Related
The context:
I stumbled upon a minor crash while testing a ListView of Dismissibles in Flutter. When swiping a dismissible, a Dialog is shown using the confirmDismiss option, for confirmation. This all works well, however the UI crashes when testing an unlikely use case. On the page are several options to navigate to other (named) routes. When a dismissible is swiped, and during the animation an option to navigate to a new route is tapped, the crash happens.
How to replicate the crash:
Dismiss the Dismissible
During the animation that follows (the translation of the position of the dismissible), tap on an action that brings you to a
new route. The timeframe to do this is minimal, I've extended it in the example.
The new route loads and the UI freezes
For reference, this is the error message:
AnimationController.reverse() called after AnimationController.dispose()
The culprit is the animation that tries to reverse when it was already disposed:
package:flutter/…/widgets/dismissible.dart:449
Things I've tried:
Initially, I tried checking this.mounted inside the showDialog builder but quickly realised the problem is not situated there.
Another idea was to circumvent the problem by using CancelableOperation.fromFuture and then cancelling it in the dispose() method of the encompassing widget, but that was to no avail.
What can I do solve or at least circumvent this issue?
The code (can also be found and cloned here):
// (...)
class _DimissibleListState extends State<DimissibleList> {
int childSize = 3;
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: childSize,
itemBuilder: (context, index) {
if (index == 0) {
return _buildNextPageAction(context);
}
return _buildDismissible();
},
),
);
}
Widget _buildNextPageAction(context) {
return FlatButton(
child: Text("Go to a new page"),
onPressed: () => Navigator.of(context).pushNamed('/other'),
);
}
Dismissible _buildDismissible() {
GlobalKey key = GlobalKey();
return Dismissible(
key: key,
child: ListTile(
title: Container(
padding: const EdgeInsets.all(8.0),
color: Colors.red,
child: Text("A dismissible. Nice."),
),
),
confirmDismiss: (direction) async {
await Future.delayed(const Duration(milliseconds: 100), () {});
return showDialog(
context: context,
builder: (context) {
return Dialog(
child: FlatButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text("Confirm dismiss?"),
),
);
},
);
},
resizeDuration: null,
onDismissed: (direction) => setState(() => childSize--),
);
}
}
I had almost same problem with confirmDismiss ,in my case I was using (await Navigator.push() ) inside of confirmDismiss to navigate to another screen but in return I faced this error :
AnimationController.reverse() called after
AnimationController.dispose()
so to solve my problem inside of confirmDismiss I call a future function out side of confirmDismiss (without await ) and then add return true or false after that function call to finish animation of confirmDismiss.
I'm new on flutter, I have a Homepage where I have a Drawer menu and body list content.
DRAWER MENU => On tap item list of drawer menu I'm loading a PAGE web URL and on tap BACK it returns to my homepage. So it works very well.
BODY LIST CONTENT => On tap item list it loads the page web URL well BUT when I won't return back to my homepage it returns a black screen :(
Homepage.dart
class HomePage extends StatefulWidget{
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return _HomePage();
}
}
class _HomePage extends State<HomePage>{
#override
Widget build(BuildContext context) {
// TODO: implement build
var globalContext = context;
return Scaffold(
appBar: AppBar(
title: Text(
'Benvenuto',
style: TextStyle(color: Colors.white)
),
backgroundColor: Color(0xFF4035b1),
),
drawer: Drawer(
child: new Column(
children: <Widget>[
new UserAccountsDrawerHeader(
accountName: Text('VIA ALBERTO POLIO 54'),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Color(0xFF4268D3),
Color(0xFF584CD1)
],
begin: FractionalOffset(0.2, 0.0),
end: FractionalOffset(1.0, 0.6),
stops: [0.0, 0.6],
tileMode: TileMode.clamp
)
),
accountEmail: Text('ORARI: LUNEDI - VENERDI 9:30 / 19:30'),
currentAccountPicture: new CircleAvatar(
radius: 50.0,
backgroundColor: const Color(0xFF778899),
backgroundImage: AssetImage("assets/img/icon_logo.jpg"),
)
),
// This list work well!
ListTile(
leading: new Icon(Icons.arrow_forward_ios),
title: new Text("TEST"),
onTap: () {
Navigator.of(context).pop();
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => Page("title", "www.google.com")));
}
)
],
),
),
// The menu on my body load well the page web url but doesn't return back to my homepage.
body: new Column(
children: <Widget>[
ListTile(
leading: new Icon(Icons.arrow_forward_ios),
title: new Text("TEST"),
onTap: () {
Navigator.of(context).pop();
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => Page("title", "www.google.com")));
}
)
])
);
}
}
Page.dart
class Page extends StatelessWidget{
final String titleText;
final String urlSource;
Page(this.titleText, this.urlSource);
#override
Widget build(BuildContext context) {
// TODO: implement build
return new WebviewScaffold(
url: urlSource,
appBar: new AppBar(
title: Text(titleText),
),
withZoom: true,
withLocalStorage: true,
hidden: true,
);
}
}
main.dart
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.green,
),
home: HomePage()
);
}
}
Thank you for your help guys!
You shouldn't be using Navigator.pop() just before Navigator.push().
If replacing the current page with a new one is what you want, you can use Navigator.of(context).pushReplacement().
If you only want to navigate to a new route delete the pop method and only use push
The real problem here is that when you're using Navigator.pop() you're removing it from the "pages stack". When you're using Navigator.pop() at the Drawer(), the ".pop" function removes the Drawer and keeps the main page.
But at the time you use it with the ListTile(), which is part of the "main body" of the page, you just remove it.
Whatever collapses the main page when pressed, such a Drawer, Dialog or even a Keyboard, will be removed using Navigator.pop(), any other thing that is at the page which implements the "Navigator.pop()" will remove the page instead.
Navigator.of(context).pop();
this one is popping the screen in the flutter.
you can refer this doc as well https://api.flutter.dev/flutter/widgets/Navigator/pop.html
your home page doesn't have any stack behind it so when you have written Navigator.of(context).pop(); then it will pop the home page where there is not anything and it always shows the blank screen.
when you have tried Navigator.of(context).pop(); in the drawer then it has home page as a stack in the flutter which is the home page in your case and it will pop to the home page and show the blank page.
I have problem with showDialog, when i press nothing happens but if i use Navigator.pushNamed(context, "/screen1") it works. I can not run Navigator.pop(context), it does not return any errors.
_showDialog(BuildContext context) {
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: new Text("Alert Dialog title"),
actions: <Widget>[
new FlatButton(
child: new Text("Back"),
onPressed: () {
//Navigator.pushNamed(context, "/screen1");
Navigator.pop(context);
},
),
],
);
});}
In my build() :
IconButton(
iconSize: 30.0,
onPressed: () => _showDialog(context),
icon: Icon(
Icons.clear,
color: Colors.white,
),
)
had the same issue .having useRootNavigator: false, in showDialog params solved my issue .
Use pop() two times:-
Navigator.of(context).pop();
Navigator.of(context).pop();
Reason: first pop function dismiss the dialog and Second pop function close the screen
The above answer should be accepted for this question, just elaborating the above answer
return showDialog(
context: context,
useRootNavigator: false, //this property needs to be added
builder: (BuildContext context) {....});
Try calling Navigator.of(context).pop(); instead of Navigator.pop(context);
For closing dialogs, you can also use:
Navigator.pop(context, true);
Source: https://docs.flutter.io/flutter/widgets/Navigator/pop.html
For those who have nested/multiple Navigators, you can also use the pop method as shown below (notice the named parameter rootNavigator set to true):
Navigator.of(context, rootNavigator: true).pop();
As suggested by others, I tried setting the useRootNavigator in the showDialog as false but this way the barrierColor wasn't covering the whole screen (it was only covering the screen displayed by Navigator object the dialog was in).
In summary, you can use the way showed above if you have nested navigators and want to achieve having the barrierColor covering the whole screen.
I am currently making an app with lists of a Card widgets. This is a code snippet:
new GestureDetector(
onLongPress: () {
showAlert();
//pops up an AlertDialog
},
child: new Card(child: new Text("Hello"))
)
Is it possible to make the Card widget touchable, so the user can feel/see that the card is (long)tapped? I am searching for an 'InkWell-something result'.
Edit: I want to have a splash effect on my Card widgets when I longpress them.
An example of the splash effect that I mean is given in the following GIF:
Use this
return Card(
child: InkWell(
onTap: () {},
onLongPress: () {}
child: Container(),
));
Our app is built on top of Scaffold and to this point we have been able to accommodate most of our routing and navigation requirements using the provided calls within NavigatorState (pushNamed(), pushReplacementNamed(), etc.). What we don't want though, is to have any kind of 'push' animation when a user selects an item from our drawer (nav) menu. We want the destination screen from a nav menu click to effectively become the new initial route of the stack. For the moment we are using pushReplacementNamed() for this to ensure no back arrow in the app bar. But, the slide-in-from-the-right animation implies a stack is building.
What is our best option for changing that initial route without animation, and, can we do that while also concurrently animating the drawer closed? Or are we looking at a situation here where we need to move away from Navigator over to just using a single Scaffold and updating the 'body' directly when the user wants to change screens?
We note there is a replace() call on NavigatorState which we assume might be the right place to start looking, but it's unclear how to access our various routes originally set up in new MaterialApp(). Something like replaceNamed() might be in order ;-)
What you're doing sounds somewhat like a BottomNavigationBar, so you might want to consider one of those instead of a Drawer.
However, having a single Scaffold and updating the body when the user taps a drawer item is a totally reasonable approach. You might consider a FadeTransition to change from one body to another.
Or, if you like using Navigator but don't want the default slide animation, you can customize (or disable) the animation by extending MaterialPageRoute. Here's an example of that:
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyCustomRoute<T> extends MaterialPageRoute<T> {
MyCustomRoute({ WidgetBuilder builder, RouteSettings settings })
: super(builder: builder, settings: settings);
#override
Widget buildTransitions(BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
if (settings.isInitialRoute)
return child;
// Fades between routes. (If you don't want any animation,
// just return child.)
return new FadeTransition(opacity: animation, child: child);
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Navigation example',
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case '/': return new MyCustomRoute(
builder: (_) => new MyHomePage(),
settings: settings,
);
case '/somewhere': return new MyCustomRoute(
builder: (_) => new Somewhere(),
settings: settings,
);
}
assert(false);
}
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Navigation example'),
),
drawer: new Drawer(
child: new ListView(
children: <Widget> [
new DrawerHeader(
child: new Container(
child: const Text('This is a header'),
),
),
new ListTile(
leading: const Icon(Icons.navigate_next),
title: const Text('Navigate somewhere'),
onTap: () {
Navigator.pushNamed(context, '/somewhere');
},
),
],
),
),
body: new Center(
child: new Text(
'This is a home page.',
),
),
);
}
}
class Somewhere extends StatelessWidget {
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Text(
'Congrats, you did it.',
),
),
appBar: new AppBar(
title: new Text('Somewhere'),
),
drawer: new Drawer(
child: new ListView(
children: <Widget>[
new DrawerHeader(
child: new Container(
child: const Text('This is a header'),
),
),
],
),
),
);
}
}
Use PageRouteBuilder like:
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (_, __, ___) => Screen2(),
transitionDuration: Duration.zero,
),
);
And if you want transition, simply add following property to above PageRouteBuilder, and change seconds to say 1.
transitionsBuilder: (_, a, __, c) => FadeTransition(opacity: a, child: c),