How to get the number of routes in Navigator's stack - flutter

Is there a way to know if the current page is the last page in the Navigator Stack and calling Navigator.pop() at this point will close the app?

You can use this code to check if the route is the first :
ModalRoute.of(context).isFirst
so the full code will be
if(! ModalRoute.of(context).isFirst)
Navigator.pop();

It doesn't close the app it destroys the last route shows a black screen.
you can close the app using this: Flutter how to programmatically exit the app
and you can't access the stack or history because it's private in Navigator class Navigator._history but you can use this workaround to check if the current route is the last one or not:
Future<bool> isCurrentRouteFirst(BuildContext context) {
var completer = new Completer<bool>();
Navigator.popUntil(context, (route) {
completer.complete(route.isFirst);
return true;
});
return completer.future;
}

I found this in the source of the AppBar widget.
final ModalRoute<dynamic>? parentRoute = ModalRoute.of(context);
final bool canPop = parentRoute?.canPop ?? false;
When canPop is false, you are on the root screen.

If you just want to handle something before the application exits. Like showing an confirm dialog you could use WillPopScope.
Example
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: _showDialog,
child: Scaffold(
body: Center(
child: Text("This is the first page"),
),
),
);
}
Future<bool> _showDialog() {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text("Are you sure?"),
content: Text("You want to exit app"),
actions: <Widget>[
FlatButton(
child: Text("Yes"),
onPressed: () => Navigator.of(context).pop(true),
),
FlatButton(
child: Text("No"),
onPressed: () => Navigator.of(context).pop(false),
)
],
);
}) ?? false;
}

Related

How to dismiss AlertDialog after Navigator.push?

I am call Navigator.push() after user press button on AlertDialog. But when user press button AlertDialog remain open and on top of new page.
How to dismiss AlertDialog after user press button?
Future<void> _showMyDialog() async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: Text('AlertDialog Title'),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text('This is a demo alert dialog.'),
Text('Would you like to approve of this message?'),
],
),
),
actions: <Widget>[
FlatButton(
child: Text('Approve'),
onPressed: () async {
await Navigator.push(
context,
MaterialPageRoute(builder: (context) => Page()),
);
Navigator.of(context).pop();
},
),
],
);
},
);
}
await _showMyDialog();
The comment saying to call pop is probably the easiest way to do this.
Another thing to consider next is if you want them to be able to stay on the same page. Here is a way to do both of these if you get beyond the => NewPage() style of navigation on your app. It's more commonly used for Drawers, of course.
Happy coding!
onTap: () {
newRouteName = "/form_check";
// if the current route is the exact location we're at (first on the stack), mark that
Navigator.popUntil(context, (route) {
if (route.settings.name == newRouteName) {
isNewRouteSameAsCurrent = true;
} else {
isNewRouteSameAsCurrent = false;
}
return true;
});
// if it isn't, go to the new route
if (!isNewRouteSameAsCurrent) {
Navigator.pushNamed(context, newRouteName);
}
// again if it is, just pop the drawer/dialog away
else {
Navigator.pop(context);
}
}

How can I use "showDialog" in order to propagate data backwards in Flutter?

Future<bool> show(BuildContext context) async {
return Platform.isIOS
? await showCupertinoDialog<bool>
(context: context, builder: (context)=>this)
:await showDialog<bool>(
context: context,
builder: (context) => this,
);
}
Can anyone help me to understand the term 'this',what does 'this' refer to and how does showDialog works that it returns Future.I tried to read documentation but still couldn't understand it?Is it the same as AlertDialog widget?
well, it's pretty much what the documentation said, it shows a material dialog above the current content of your app, as for this it passes the current widget as child for the dialog, as for the returned value is just like normal page navigation that when you call pop(context, {value}) method you can also return a value, so that value that inside pop will be returned from the dialog.
here is an example below:
class DialogTest extends StatefulWidget {
#override
_DialogTestState createState() => _DialogTestState();
}
class _DialogTestState extends State<DialogTest> {
// the value that will be typed to the dialog
String dialogText;
// the value that will be returned from the dialog
String returnedFromDialog;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Sample Code'),
),
body: Center(
child:
Text('You got this value from the dialog => $returnedFromDialog'),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
returnedFromDialog = await showDialog<String>(
context: context,
builder: (context) {
return AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextField(
onChanged: (value) => dialogText = value,
),
FlatButton(
onPressed: () {
setState(() => Navigator.pop(context, dialogText));
},
child: Text(
'Close dialog',
style: TextStyle(color: Colors.red),
),
)
],
),
);
});
},
child: Icon(Icons.open_in_browser),
),
);
}
}

refresh data on home page using future builder on button click - flutter

i have an app with two screens, home and update page.
The home page displays a list of items and the update page updates the items.
I am having difficulties refreshing the home page to display current updates when I pop back to it.
How can I refresh the home page when I route back to it.
See the code to navigate to update page
// home page
// build the list widget
Widget _buildTaskWidget(task) {
return ListTile(
leading: Icon(Icons.assignment),
title: Text(task['name']),
subtitle: Text(task['created_at']),
onTap: () async {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => UpdateTask(task: task),
),
);
await fetchAllTask();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBar(
title: Text(widget.title),
),
body: FutureBuilder(
future: fetchAllTask(),
builder: (context, snapshot) {
if (snapshot.hasData) {
List tasks = snapshot.data;
listItems = tasks;
return _buildTaskList(tasks);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return Center(
child: ShowLoader(),
);
}),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => AddTask()));
},
tooltip: 'Add Task',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
// update page to navigate back to home page
onPressed: () async {
var res = await updateNewTask(_taskTextInput.text,
_vendorTextInput.text, _amountTextInput.text, id);
print(res);
Navigator.pop(context);
},
FutureBuilder only runs the asynchronous task when its parent is built. To force a rebuild, you can call setState() after Navigating to the next page. Doing so refreshes the current Screen before navigating to the next.
Navigator.of(context).push(...).then((_) => setState(() {}));
Another approach that you can also consider looking into is with the use of StreamBuilder - this Widget rebuilds when change in Stream is detected.

Flutter Promise with modal Loading animation while async task

I am doing a async API call to fetch some data before navigating to the next screen. This works fine, but within this API call the user could edit the current screen. I want to show some loading animation modal while the async task is beeing active. Is there a way to do this by using the Promise functionality?
_apiCall(...).then((retVal) {
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => SecondScreen(retVal)));
});
Ok i found a workaround for that:
return MaterialApp(
home: Scaffold(
body: isLoading ? getLoadingBar() : ListView(
children: <Widget>[
...,
...,
],
),
),
);
Widget getLoadingBar() {
return new Container(
color: Colors.blue,
child: Center(
child: Loading(indicator: BallPulseIndicator(), size: 100.0),
),
);
}
inside my RaisedButton:
onPressed: () {
setState() {
isLoading = true;
}
_apiCall(...).then((retVal) {
setState() {
isLoading = false;
}
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => SecondScreen(retVal)));
});
}

Flutter update state coming from another screen

I have two Screens. In the first one, there is a ListView. In the first element I display the value of the variable selectedObject.
When the ListTile is pressed, a second Screen is opened. I want to update the selectedObject value after returning from the second screen.
I need to assign the value of result to the selectedObject variable.
I think I have to call the setState method but I don't know how.
Here is my code:
class _FilterTaskState extends State<FilterTask> {
List taskList;
String selectedObject = "initial value";
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: _buildAppBar(context, "AppBar Title"),
body: Center(
child: new ListView(
children: <Widget>[
new ListTile(
leading: new Icon(Icons.home, color: Colors.black),
title: new Text("Selected Object", style: styleTitle),
subtitle: new Text(selectedObject),
trailing: new Icon(Icons.play_arrow, color: Colors.black),
onTap: () => _navigateToFilterObject(context),
),
...
],
)
),
);
}
}
_navigateToFilterObject(BuildContext context) async {
final result = await Navigator.push(context,
MaterialPageRoute(builder: (context) => FilterObject()),);
/// I want to set the 'selectedObject' value
/// selectedObject = result;
}
On your FilterObject widget returns the value when you select the item like this:
Navigator.of(context).pop(theValueYouWantToReceive);
And you will get the result inside result variable:
final result = await Navigator.push(context, MaterialPageRoute(builder: (context) => FilterObject()),);
Final code
_navigateToFilterObject(BuildContext context) async {
final result = await Navigator.push(context,
MaterialPageRoute(builder: (context) => FilterObject()),);
//refresh the state of your Widget
setState(() {
selectedObject = result;
});
}
_navigateToFilterObject must be inside your _FilterTaskState class
Read about Navigator pop : https://docs.flutter.io/flutter/widgets/Navigator/pop.html