Flutter Navigation pop to index 1 - flutter

I am recursively adding routes to the navigator. There could be 20 views or more. Pop works as advertised, but I would like to pop to index 1 and remove all push history. is there a way to replace this pop command with something like... returntoIndex0...
new ListTile(
title: new RaisedButton(
child: new Text("POP"),
onPressed: () {
var route = new MaterialPageRoute(
builder: (BuildContext context) =>
new NextPage3(value:"hi there from 3"),
);
Navigator.pop(context);
},
),
),

If you do not use named routes, you can use
Navigator.of(context).popUntil((route) => route.isFirst);

In case you know exactly how many pops should be performed:
For example for 2 pops:
count = 0;
Navigator.popUntil(context, (route) {
return count++ == 2;
});

If you are using MaterialPageRoute to create routes, you can use this command:
Navigator.popUntil(context, ModalRoute.withName(Navigator.defaultRouteName))
Navigator.defaultRouteName reflects the route that the application was started with. Here is the piece of code that illustrates it in more detail:
child: InkWell(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image(
image: AssetImage('assets/img/ic_reset.png'),),
Text('Change my surgery details',
style: TextStyle(color: Colors.blue, decoration: TextDecoration.underline),),
],
),
onTap: () =>
Navigator.popUntil(context, ModalRoute.withName(Navigator.defaultRouteName))
),
Hope this helps.

For me I used this when pushing a new page:
widget = MyWidget();
Route route = CupertinoPageRoute(builder: (context) => widget, settings:RouteSettings(name: widget.toStringShort()));
Navigator.push(context, route);
Then to go back to specific page:
Navigator.of(context).popUntil((route) => route.settings.name == "MyWidget");

Use popUntil method as mentioned in the docs
Typical usage is as follows:
Navigator.popUntil(context, ModalRoute.withName('/login'));

Here Dashboard() is the screen name. So this will pop out all the screens and goto Dashboard() screen.
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (c) => Dashboard()),
(route) => false)

You can also do it like this
Navigator.of(context)
.pushNamedAndRemoveUntil('/Destination', ModalRoute.withName('/poptillhere'),arguments: if you have any);
The use case is to go the desired screen and pop the screens in between as you require.
For more info, you can check this Post Explaining other Solutions

I tried other answers in this post, and somehow they causing the following exception.
To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.
The relevant error-causing widget was
MaterialApp
lib/main.dart:72
When the exception was thrown, this was the stack
#0 Element._debugCheckStateIsActiveForAncestorLookup.<anonymous closure>
package:flutter/…/widgets/framework.dart:3781
#1 Element._debugCheckStateIsActiveForAncestorLookup
package:flutter/…/widgets/framework.dart:3795
#2 Element.dependOnInheritedWidgetOfExactType
package:flutter/…/widgets/framework.dart:3837
#3 Theme.of
package:flutter/…/material/theme.dart:128
#4 XXxxXX.build.<anonymous closure>
package:xxx/widgets/xxx.dart:33
...
════════════════════════════════════════════════════════════════════════════════
The following answer fixed the issue.
https://stackoverflow.com/a/52048127/2641128
Navigator.pushNamedAndRemoveUntil(context, '/', (_) => false);

//========================================================
new ListTile(
title: new RaisedButton(
child: new Text("POP until"),
onPressed: () {
var route = new MaterialPageRoute(
builder: (BuildContext context) =>
new NextPage3(value:"hi there from 3"),
);
//Navigator.pop(context, ModalRoute.withName('/'));
Navigator.popUntil(context,ModalRoute.withName('/'));
},
),
),
//========================================================
replace .pop with .popUntil, actually works very elegantly.

This always gets me the expected result.
And will pop to route of current Navigator stack
Navigator.of(context, rootNavigator: true).pop();

This will pop all the routes until the main default route and push to your destination route.
Navigator.pushNamedAndRemoveUntil(context, "destination_route", ModalRoute.withName('/'));

Example for two pops, using cascade operator:
Navigator.of(context)..pop()..pop();

Related

Flutter Dialog: [VERBOSE-2:ui_dart_state.cc(198)] Unhandled Exception: Null check operator used on a null value

I'm trying to fetch a value when a ListView Item in Page1 is clicked:
...
child: ListTile(
title: Text(title),
onTap: () {
Navigator.pop(context, <String>[title]);
},
),
...
Here, title is a String.
This is popped into Page 0:
...
CupertinoButton(
child: Icon(CupertinoIcons.add),
onPressed: () async {
var value = await Navigator.push(
context,
CupertinoPageRoute(
builder: (context) => const Page1(),
),
);
print(value); // Added for debugging
showNewDialog(context, value);
},
...
),
And this is my showNewDialog method:
Future<dynamic> showNewDialog(
BuildContext context, String name) {
return showCupertinoDialog(
context: context,
builder: (BuildContext context) {
return CupertinoAlertDialog(
title: Text(name),
content: ...
actions: [
CupertinoDialogAction(
child: Text("Cancel"),
isDestructiveAction: true,
onPressed: () {
Navigator.pop(context);
},
),
CupertinoDialogAction(
child: Text("Add"),
onPressed: () {
...
Navigator.pop(context, [...]);
},
),
],
);
},
);
}
tldr; When I click a button on Page0, It opens Page1 and I can click a ListView item which basically sends the title (String) of that item back to Page0 so that I can create a CupertinoAlertDialog with title as the title of that Dialog.
When I try to do this, I get the following error:
[VERBOSE-2:ui_dart_state.cc(198)] Unhandled Exception: Null check operator used on a null value
#0 StatefulElement.state (package:flutter/src/widgets/framework.dart:4926:44)
#1 Navigator.of (package:flutter/src/widgets/navigator.dart:2542:47)
#2 showCupertinoDialog (package:flutter/src/cupertino/route.dart:1291:20)
#3 showNewDialog (package:sid/utils.dart:37:10)
#4 _Page0State.build.<anonymous closure> (package:sid/page_0.dart:61:13)
The print value prints the right value, so there is no null value being passed in.
Also, I haven't used the '!' operator anywhere in my code. The error seems to point to showCupertinoDialog, which is weird.
Any help will be appreciated.
Thanks :D
You can put the variable static and put the value that you want.
And after that when return to the main page u can check if the var is not empty
And if it's not than your condition :)

How to decide between open a dialog or pushing a new Page depending on size width in Flutter?

I am working on refactoring an existing Flutter app designed for mobile to act responsive on web.
I think I fully understand MediaQuery and LayoutBuilder but I was stack with this issue about different resulting layouts that cannot be resolved inside the widget build method, or at least I don't see how to do it.
For explaining my problem lets say we have a LoginPage which has a Register button and when the button gets pressed we want to:
Push a new Page for register on the Navigator if we are in mobile or tablet size
Open a Dialog with the register page content if we are in desktop or web size
So this is the goal, if we are in mobile open a new page if we are in desktop open a dialog.
Sounds pretty simple if we are thinking statically, I mean if the size of the screen will not change continuoslly which is not the case. And at this point is where I am now, not sure how to resolve this when the size change after both pages are open.
Let's see some code to try to show how I was thinking this and probably you could give me some advice.
Here is a code snipped for my LoginPage where you see the void function we call when user press singin button
typedef VoidActionCallback = Function(BuildContext context, RegisterResourceBloc);
class LoginPage extends StatelessWidget with CoolAlertUtils {
final VoidActionCallback onRegister;
LoginPage({this.onRegister});
Widget _buildSignupBtn(BuildContext context, RegisterResourceBloc resourceBloc) {
return GestureDetector(
onTap: () {
this.onRegister(context, resourceBloc);
},
child: RichText(
text: TextSpan(
children: [
TextSpan(
text: LocaleKeys.noAccount.tr() + '? ',
style: TextStyle(
color: Colors.white,
fontSize: 18.0,
fontWeight: FontWeight.w400,
),
),
TextSpan(
text: LocaleKeys.signUp.tr(),
style: TextStyle(
color: Colors.white,
fontSize: 18.0,
fontWeight: FontWeight.bold,
),
),
],
),
),
);
}
Then we have an ActionProvider that defines the actions that will occurs depending on the size:
class RegisterActionProvider {
final RegisterPage registerPage;
RegisterActionProvider({RegisterResourceBloc resourceBloc}):
this.registerPage=RegisterPage(
resourceBloc: resourceBloc,
registerBloc: RegisterBloc(),
);
registerActionMobile(BuildContext context){
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return this.registerPage;
}));
}
registerActionWeb(BuildContext context){
ShowDialog.show(
context: context,
height: 800,
width: 600,
child: this.registerPage);
}
}
Now I have a ResponsiveAction that defines what action will be executed depending on size:
class ResponsiveRegisterAction{
final List<DeviceType> breakpoints;
ResponsiveRegisterAction({this.breakpoints});
DeviceTypeAction buildDeviceTypeAction(RegisterResourceBloc bloc, BuildContext context){
RegisterActionProvider provider=RegisterActionProvider(resourceBloc: bloc);
DeviceTypeAction result=DeviceTypeAction(
breakpoints: this.breakpoints,
actions: [
DeviceAction(
name: 'MOBILE',
builder: (context) => provider.registerActionMobile(context)
),
DeviceAction(
name: 'TABLET',
builder: (context) => provider.registerActionMobile(context)
),
DeviceAction(
name: 'WEB',
builder: (context) => provider.registerActionWeb(context)
)
]
);
return result;
}
}
And when the LoginPage builds it receives in its constructor the corresponding ActionProvider as we can see here in a code snipped on the routes definition of the app:
routes: {
'/': (BuildContext context) => LoadingPage(),
'/login': (context) => ResponsiveLoginPage(loginPage: LoginPage(
onRegister: (context, RegisterResourceBloc bloc) =>
ResponsiveRegisterAction(
breakpoints: DeviceTypeConfig.instance.devices
).buildDeviceTypeAction(bloc, context).execute(context),
)
),
'/logout': (context) => LogoutPage(),
'/sales': (context) => SalesDashboard(),
'/payments': (context) => PaymentsDashboard(),
'/inventory': (context) => StockCoverageDashboard(),
'/dashboard': (context) => MainDashboard(),
'/aboutOf': (context) => AboutOfPage(),
'/configuration': (context) => ConfigurationPage(),
},
The magic was made inside of the execute method of the DeviceTypeAction which decides which action will execute depending on screen size.
This works fine in the sense that it acts as expected when the user clicks the signin button depending of the current screen size, but when the screen size change after the signin content was show nothing happens, and is ok, we do nothing to tacle that case.
And here we are finally arriving to the issue, how can we resolve the change of size after the register page was show?
I was exploring different alternatives but honestly I don't have a clue.
Basically because I know I can use a LayoutManager to rebuild the widget when the size change but this doesn't depends exclusively on rebuild the widget it self, it needs to change the widget tree, because for example if we are on web and we have the dialog for register open and the user resize the browser and shrink the width we need to be able to close the dialog and push a new page on the navigator instead.
So, probably there is a better way of doing this that's why I m asking.
My way of doing this was to open a dialog on each case but reducing the padding between screen edge if it's on a small screen.
Probably not the best way but easy to implement and easy to understand and it's working ¯_(ツ)_/¯ If it's help code below using riverpod
#override
Widget build(BuildContext context) {
return Consumer(
builder: (context, watch, child) {
final isSmallScreen =
watch(deviceType).maybeWhen(smartphone: (_) => true, orElse: () => false);
return Opacity(
opacity: isSmallScreen?1:0.85,
child: AlertDialog(
insetPadding: EdgeInsets.symmetric(horizontal: isSmallScreen?0:30, vertical:isSmallScreen?0: 10),
contentPadding: EdgeInsets.fromLTRB(isSmallScreen?5:20.0, 16.0, isSmallScreen?5:20.0, 20.0),
...

on pressing back button on device my app should close without going to any previous screens in Flutter

I am wondering if anyone knows how to use the device back button to exit the app without going back to any previous pages/routes.
Future<bool> _onBackPressed() {
return showDialog(
context: context,
builder: (context) => new AlertDialog(
title: new Text('Are you sure?'),
content: new Text('Do you want to exit an App'),
actions: <Widget>[
new GestureDetector(
onTap: () => Navigator.of(context).pop(false),
child: Text("NO"),
),
SizedBox(height: 16),
new GestureDetector(
onTap: () {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => WelcomeScreen()),
);
},
child: Text("YES"),
),
],
),
) ??
false;
}
To programmatically exit flutter apps, you need to pop the systemNavigator like follow :
SystemNavigator.pop()
SystemNavigator is available after importing Services :
import 'package:flutter/services.dart';
An other method is to use exit(0) which would terminate the app but is usually not user Interface friendly especially on iOS.
You can exit your app using following ways:
SystemChannels.platform.invokeMethod<void>('SystemNavigator.pop', animated)
exit(0)
SystemNavigator.pop()
Although all three ways works but the preferred way of doing this is to use SystemNavigator.pop(). The method looks like this:
static Future<void> pop({bool animated}) async {
await SystemChannels.platform.invokeMethod<void>('SystemNavigator.pop', animated);
}
exit(0)is not a recommended way as it immediately terminates the process running in dart VM and looks a bit like your app is crashed.

Showing snackbar from alert dialog

I'm at a loss with this one. So I know that to show a snack bar, you have to have access to a build context whose ancestor is a scaffold. To solve this I usually just make a separate widget within the scaffold within which a new build context can be called. However, I can't seem to get this to work when I use an alert dialog.
The 'child' widget i've made under the scaffold looks like this:
class DeleteButton extends StatelessWidget {
DeleteButton({#required this.vm, #required this.popCallback});
final AddJobVM vm;
final Function popCallback;
#override
Widget build(BuildContext context) {
final continueCallBack = () async {
print("deleting ${vm.jobName}");
ToasterBundle toast;
toast = await vm.deleteJob();
print(toast.success);
Scaffold.of(context).showSnackBar(generateSnackBar(toast));
await Future.delayed(
Duration(seconds: 2),
);
if (toast.success) {
popCallback();
}
};
return Padding(
padding: EdgeInsets.only(right: kStandardPadding),
child: GestureDetector(
onTap: () {
showDialog(
context: context,
builder: (context) {
return AlertDialogueBlurredBG(
title: 'Delete Job',
content: 'Are you sure you want to delete this job?',
continueCallBack: continueCallBack,
);
});
},
child: Icon(
Icons.delete_outline,
color: kColorWhite,
size: 28,
),
),
);
}
}
But I'm getting an error when I call the 'continueCallBack':
[VERBOSE-2:ui_dart_state.cc(157)] Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe.
At this point the state of the widget's element tree is no longer stable.
To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.
#0 Element._debugCheckStateIsActiveForAncestorLookup.<anonymous closure> (package:flutter/src/widgets/framework.dart:3781:9)
#1 Element._debugCheckStateIsActiveForAncestorLookup (package:flutter/src/widgets/framework.dart:3795:6)
#2 Element.findAncestorStateOfType (package:flutter/src/widgets/framework.dart:3914:12)
#3 Scaffold.of (package:flutter/src/material/scaffold.dart:1453:42)
#4 DeleteButton.build.<anonymous closure> (package:upworkv2/screens/jobs/add_edit_job_screen.dart:615:16)
<asynchronous suspension>
#5 DeleteButton.build.<anonymous closure> (package:upworkv2/screens/jobs/add_edit_job_scree<…>
I would have thought that using a call back which references the build context outside of the alert dialog would have worked but no dice. Any ideas on where I'm going wrong here?
Builder Widget will help in this case, just see How I use & implement it,
body: Builder(
builder: (BuildContext innerContext) {
return RaisedButton(
onPressed: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Are you sure?'),
content: Text('Do you want to go to background?'),
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('NO')),
FlatButton(
onPressed: () {
Scaffold.of(innerContext).showSnackBar(SnackBar(
content: Text('Added added into cart'),
duration: Duration(seconds: 2),
action:
SnackBarAction(label: 'UNDO', onPressed: () {}),
));
},
child: Text('YES'))
],
),
);
},
);
},
),
This exception happens because you are using the context of the widget that instantiated Scaffold. Not the context of a child of Scaffold.
Output:

Unable to navigate to home page in flutter

I have an app comprising of home and update screens.
I am unable to navigate back to the home screen from the update screen.
See below code for home screen
// build the list widget
Widget _buildTaskWidget(task) {
return ListTile(
leading: Icon(Icons.assignment),
title: Text(task['name']),
subtitle: Text(task['created_at']),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => UpdateTask(task: task),
),
);
}
);
}
See below code for the update screen
#override
Widget build(BuildContext context) {
// final Task task = ModalRoute.of(context).settings.arguments;
return Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBar(
title: Text('Update Task'),
),
body: ListView(
children: <Widget>[
inputWidget(),
inputWidgetForVendor(),
inputWidgetForAmount(),
Container(
margin: EdgeInsets.fromLTRB(45, 1, 45, 1),
child: RaisedButton(
color: Colors.blueAccent,
child: Text('Update Task', style: TextStyle(color: Colors.white)),
onPressed: () async {
var res = await updateNewTask(_taskTextInput.text, _vendorTextInput.text, _amountTextInput.text, id);
print(res);
Navigator.pop(context);
},
),
)
],
)// This trailing comma makes auto-formatting nicer for build methods.
);
}
If I remove the current onPressed function and replace with this below, it works
onPressed: () { Navigator.pop(context); },
What am I doing wrong in the initial function?
The update function successfully updates the list items, however I am unable to navigate back.
See below error logs:
E/flutter (27123): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'List<dynamic>'
E/flutter (27123): #0 updateNewTask (package:task/repository/services.dart:67:10)
E/flutter (27123): <asynchronous suspension>
Please help.
Maybe outsourcing your async update function is a simple solution at this point, when you want to instantly go back to your home screen. You could print the update directly in the function then.
Just leave onpressed() as it is.
onPressed: () {
updateNewTask(_taskTextInput.text, _vendorTextInput.text, _amountTextInput.text, id);
Navigator.pop(context);
},