Showing snackbar from alert dialog - flutter

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:

Related

Flutter context error after multiple pops on dialogs

I have a function called from a button in one of my menu pages that builds an AlertDialog passing a context.
The dialog contains a button that calls a function (called testFunction) that:
first disposes the current dialog using the passed context;
then creates a new loading dialog;
then calls an async function which, when done, disposes the current loading dialog and creates a new final dialog.
But it gives me this error when I try to build the loading dialog on the third step:
E/flutter ( 2550): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe.
E/flutter ( 2550): At this point the state of the widget's element tree is no longer stable.
E/flutter ( 2550): 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 function called from the menu button:
static void buildDeckPurchaseDialog(BuildContext context) {
showDialog(context: context, builder: (BuildContext context) {
return AlertDialog(
content: SizedBox(
width: 80,
height: 130,
child: Center(
MenuAnimatedButton(
width: 110,
height: 50,
function: () => testFunction(context), // Executed on button tap
),
),
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
backgroundColor: kBoxColor,
);
});
}
testFunction() called from the button in the dialog built from the previous function:
Future<dynamic> testFunction(BuildContext context) async {
try {
// Disposing the previous dialog
Navigator.of(context).pop();
// Showing loading dialog
CustomDialogs.buildLoadingDialog(context, "Processing purchase...");
// Making the async request
return await FirebaseFunctions.instance.httpsCallable('test').call({
'test': 1,
}).then((value) {
// Disposing the loading dialog
Navigator.of(context).pop(); // <- ERROR HERE
// Building the last dialog (which is not shown)
CustomDialogs.buildSimpleDialog("End of function", context);
}).onError((error, stackTrace) => Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => const ErrorScreen())));
} on FirebaseFunctionsException {
Navigator.push(context, MaterialPageRoute(builder: (context) => const ErrorScreen()));
}
}
I think I should use didChangeDependencies() method but I don't know how.
What I was doing wrong was giving the context passed to the buildDeckPurchaseDialog function the same name as the context created by the showDialog function builder (builder: (BuildContext context)).
This way, testFunction(context) took the builder context as an argument and not the passed context.
Then write the function like this:
static void buildDeckPurchaseDialog(BuildContext passedContext) {
showDialog(context: passedContext, builder: (BuildContext context) {
return AlertDialog(
content: SizedBox(
width: 80,
height: 130,
child: Center(
MenuAnimatedButton(
width: 110,
height: 50,
function: () => testFunction(passedContext), // Executed on button tap
),
),
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
backgroundColor: kBoxColor,
);
});
}

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 wrap navigation with blocprovider.value which inside a bloc listener

BlocConsumer<VehicleCubit, VehicleState>(
listener: (context, state) {
if (state is VehicleLoaded) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) {
final registrationNumber =
controller.text.trim().toUpperCase();
return BlocProvider.value(
value: BlocProvider.of<VehicleCubit>(context),
child: VehicleDetailsScreen(
registrationNumber: registrationNumber,
),
);
},
),
);
}
},
// TODO: Fix routing
builder: (context, state) {
if (state is VehicleLoading) {
return Center(
child: CircularProgressIndicator(
valueColor:
new AlwaysStoppedAnimation<Color>(brandColor),
),
);
}
if (state is VehicleNotLoaded) {
toast('Unable to fetch vehicle, retry..');
}
return ElevatedButton(
onPressed: () {
FocusScope.of(context).unfocus();
if (controller.text.trim().isEmpty) return;
final registrationNumber =
controller.text.trim().toUpperCase();
BlocProvider.of<VehicleCubit>(context)
.fetchVehicle(registrationNumber, context);
},
child: Text(
'Proceed',
style: TextStyle(fontSize: 16),
),
style: ButtonStyle(
shape: MaterialStateProperty.resolveWith(
(states) => RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
)),
backgroundColor: MaterialStateProperty.resolveWith(
(states) => Color(0xFFE07A72)),
),
);
},
)
What is wrong here? I was trying to use navigation and pass then current cubit to its children but getting errors
Here is the error:
======== Exception caught by widgets library =======================================================
The following ProviderNotFoundException was thrown building Container(bg: MaterialColor(primary value: Color(0xff2196f3)), constraints: BoxConstraints(0.0<=w<=Infinity, h=100.0)):
Error: Could not find the correct Provider<BlocBase<dynamic>> above this BlocBuilder<BlocBase<dynamic>, dynamic> Widget
This happens because you used a `BuildContext` that does not include the provider
of your choice. There are a few common scenarios:
- You added a new provider in your `main.dart` and performed a hot-reload.
To fix, perform a hot-restart.
- The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then
other routes will not be able to access that provider.
- You used a BuildContext that is an ancestor of the provider you are trying to read.
Make sure that BlocBuilder<BlocBase<dynamic>, dynamic> is under your MultiProvider/Provider<BlocBase<dynamic>>.
This usually happens when you are creating a provider and trying to read it immediately.
For example, instead of:
```
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// Will throw a ProviderNotFoundError, because `context` is associated
// to the widget that is the parent of `Provider<Example>`
child: Text(context.watch<Example>()),
),
}
```
consider using `builder` like so:
```
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// we use `builder` to obtain a new `BuildContext` that has access to the provider
builder: (context) {
// No longer throws
return Text(context.watch<Example>()),
}
),
}
```
If none of these solutions work, consider asking for help on StackOverflow:
https://stackoverflow.com/questions/tagged/flutter
The relevant error-causing widget was:
Container file:///D:/Development/FlutterDevelopment/xpcover/lib/presentation/vehicle_insurance/vehicle_details_screen.dart:38:13
```
When the exception was thrown, this was the stack:
#0 Provider._inheritedElementOf (package:provider/src/provider.dart:332:7)
#1 Provider.of (package:provider/src/provider.dart:284:30)
#2 ReadContext.read (package:provider/src/provider.dart:610:21)
#3 _BlocBuilderBaseState.initState (package:flutter_bloc/src/bloc_builder.dart:130:36)
#4 StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:4632:57)
```
==================================================================================================

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);
},

Flutter Navigation pop to index 1

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();