Flutter context error after multiple pops on dialogs - flutter

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

Related

Flutter update refresh previous page when page has been pushed via a stateless widget

So here is the problem.
TabScreen() with 3 pages and one fabcontainer button (Stateless widget).
When pressed the fabcontainer will give you the chances of make one upload, after the upload i would like to refresh one of the page of the tabscreen.
return Container(
height: 45.0,
width: 45.0,
// ignore: missing_required_param
child: FabContainer(
icon: Ionicons.add_outline,
mini: true,
),
);
}
OnTap of the fabcontainer:
Navigator.pop(context);
Navigator.of(context).push(
CupertinoPageRoute(
builder: (_) => CreatePost(),
),
);
},
Cannot add a .then(){setState... } because it is a stateless widget and i need to set the state of a precise page, not of the fabcontainer.
Any idea?
Thanks!
Define a updateUi method inside your TabScreen (which defines the pages)
TabScreen:
void updateUi(){
// here your logic to change the ui
// call setState after you made your changes
setState(() => {});
}
Pass this function as a constructor param to your FabContainer button
FabContainer(
icon: Ionicons.add_outline,
mini: true,
callback: updateUi,
),
Define it in your FabContainer class
final Function() callback;
Call it to update the ui
callback.call();
So what Ozan suggested was a very good beginning but i could not access the stateful widget in order to set the state.
What i did on top of Ozan's suggestion was giving the state a globalkey:
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
Assigning it to the scaffold:
return Scaffold(
key: scaffoldKey,
Making the state public removing the _MyPizzasState -> MyPizzasState
Creating a method to refresh the data:
refreshData() {
pizzas = postService.getMyPizzas();
setState(() {
});
}
Assigning a key during the creation of the MyPizzaPage:
final myPizzasKey = GlobalKey<MyPizzasState>();
{
'title': 'My Pizza',
'icon': Ionicons.pizza_sharp,
'page': MyPizzas(key: myPizzasKey),
'index': 0,
},
And, how Ozan said once i received the callback :
buildFab() {
return Container(
height: 45.0,
width: 45.0,
// ignore: missing_required_param
child: FabContainer(
icon: Ionicons.add_outline,
mini: true,
callback: refreshMyPizzas,
),
);
}
void refreshMyPizzas() {
print("Refreshing");
myPizzasKey.currentState?.refreshData();
}

Two stateFullWidget error This widget has been unmounted, so the State no longer has a context

this question similar on flutter error: This widget has been unmounted, so the State no longer has a context (and should be considered defunct) flutter
i have two stateFullWidget
PersonScreen
_HandScreen
where in PersonScreen i have Column
...
PersonScreen
children [
Text('Hand'),
_HandScreen()
]
and then in HandScreen
i have DragTarget
DragTarget<String>(
onAccept: (value) async {
print('value $value');
await onAccept(value);
},
...
onAccept(value) async {
// i tried to open view alert or automatically navigator into any screen
try {
Alert(
context: context,
title: 'Finished!',
desc: 'You\'ve reached ',
image:Lottie.asset(
'assets/lotties/completed-check.json',
height: 85.0,
width: 85.0,
),
buttons: [
DialogButton(
child: Text(
"Selesai",
style: TextStyle(color: Colors.white, fontSize: 20),
),
onPressed: () => Navigator.of(context, rootNavigator: true).pop(),
color: Color.fromRGBO(0, 179, 134, 1.0),
),
],
).show();
} catch(e) {
print('error = $e'); // this throw This widget has been unmounted, so the State no longer has a context (and should be considered defunct)
}
}
but when i tried drag an item against (x2) into accept event, the alert can view or open.
i have change the instead of Alert with Navigator.pushNamed(context, MainScreen.id); the error is same.
i have tried with one stateFullWidget its look normal on running , but how i can handle it with two stateFullWidget , its posible ? because a lot of code in _HandScreen, i dont want to wrap into PersonScreen. by the way i use Provider instead of setState.

Navigator.push not working inside async method

I am trying to automatically navigate to another screen after a future method inside an async method. But it's not working on the first-page launch. I keep getting this error.
**Error**
I/flutter ( 5094): Looking up a deactivated widget's ancestor is unsafe.
I/flutter ( 5094): At this point the state of the widget's element tree is no longer stable.
I/flutter ( 5094): 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.
void getPlaceDetails(String placeId, BuildContext ctx, String type) async {
try{
await mainBloc.fetchRideEstimate(context,
mainBloc.pickUpAddress.longitude,
mainBloc.pickUpAddress.latitude,
mainBloc.destinationAddress.longitude,
mainBloc.destinationAddress.latitude).then((value){
Navigator.pop(ctx);
//keeps throwing an error here during first screen launch.
Navigator.push(
context, SlideFromLeftPageRoute(widget:
EstimatedSummaryPage()));
});
}catch(e){
print(e.toString());
}
}
print((thisPlace.placeName));
}
Build Widget
isSearchingFrom?
Expanded(child:
MediaQuery.removePadding(
context: context,
removeTop: true,
child: ListView.separated(
itemBuilder: (context, index){
return FlatButton(
padding: EdgeInsets.zero,
onPressed: () {
getPlaceDetails(pickUpPredictionlist[index].placeId,
_scaffoldKey.currentContext, "from");
},
child: PlacesListTile(
prediction: pickUpPredictionlist[index],
),
);
},
separatorBuilder: (context, index) {
return Divider(
height: 2,
);
},
itemCount: pickUpPredictionlist.length),
)):
Container(),

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:

The dialog box opens multiples time at a same time in flutter

whenever I click many times on wheel, open multiple dialog boxes at the same time.
I just want, it should be open after previous got dismissed.
I took an image and add animation on it and wrapped it with GestureDetector widget.
onTap: event i called alertDialogBox() method which is defined for Dialog box. watch above the gif image, and called the animation method with specific Condition
CODE:
Dialog box
alertDialogBox(BuildContext context) {
return showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16.0))),
contentPadding: EdgeInsets.only(top: 10.0),
content: Stack(
children: <Widget>[
....
],
),
);
});
}
The Wheel:
GestureDetector(
child: Container(
alignment: Alignment.center,
color: Colors.blue,
child: new AnimatedBuilder(
animation: _animationCtrl,
child: Container(
height:MediaQuery.of(context).size.height/2.3,
width: MediaQuery.of(context).size.width/1.3,
decoration: BoxDecoration(
color: Colors.blue,
image: DecorationImage(
image: AssetImage('assets/wheel.png', ),
fit: BoxFit.contain,
),
borderRadius: BorderRadius.all(Radius.circular(130.0)),
)
),
builder: (BuildContext context, Widget _widget) {
.......
},
),
),
onTap: ()async{
await Firestore.instance.collection('Users').document(uid).get().then((DocumentSnapshot documnet){
getIsSpin=documnet['isSpin'];
});
if(getIsSpin=="0"){
if (!_animationCtrl.isAnimating) {
//applying animation
}
DateTime now = DateTime.now();
// String lastSpinTime =DateFormat("yyyy-MM-dd hh:mm:ss").format(now);
.....//here updating isSpin value=1 and also updating spining Date time to firestore
}else {
oneDayDuration();
}
}
)
After 24 hours trying to spin the wheel
oneDayDuration():
void oneDayDuration()async{
int differenceTime;
await({
....here fetching last spin date time from firestore});
....//here getting difference hours between last spining time and current time
if(differenceTime>=24){
await({......//updating ispin=0 to firbase
})
.then((result) => {
print("Now you're able to spin"),
}).catchError((err) => print(err));
}else{
print("Please wait for 24 hours");
alertDialogBox(context);
}
}
}
Maybe this is because, you are trying to show dialog Asynchronously, where you don't have to. Just remove async, it is unnecessary while showing a simple dialog.
You better create a method that runs async in the if condition, and remove async in the onTap. This will separate your dialog code with async.
It is too late to answer this question, I came across the same scenario and solved it.
This is because the alertDialogBox function is invoked by build method every time the state is changed. you need to limit it by adding a variable to class like 'isAlertboxOpened' and make opening of alertDialogBox conditional and avoid opening multiple dialog boxes.
The following code should work
class _MyHomePageState extends State<MyHomePage> {
bool isAlertboxOpened; // add this
#override
void initState(){
isAlertboxOpened = false; // add this
}
alertDialogBox(BuildContext context) async {
setState(() => isAlertboxOpened = true); // add this
return showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16.0))),
contentPadding: EdgeInsets.only(top: 10.0),
content: Stack(
children: <Widget>[
....
// when press ok button on pressed add this
onPressed:(){
// your code
setState(() => isAlertboxOpened = false);
Navigator.of(context).pop();
}
],
),
);
});
}
void oneDayDuration()async{
int differenceTime;
await({
....here fetching last spin date time from firestore});
....//here getting difference hours between last spining time and current time
if(differenceTime>=24){
await({......//updating ispin=0 to firbase
})
.then((result) => {
print("Now you're able to spin"),
}).catchError((err) => print(err));
}else{
print("Please wait for 24 hours");
isAlertboxOpened ? (){} : // add this to make this conditional
alertDialogBox(context);
}
}
}