Reputation: 165
I have an app which exports a json object to a json file and while it's exporting, I wanted to show an alert dialog with a circular progress indicator on it. But for some reason, the alert dialog with my progress indicator is not showing up.
This is the look of my app before I export my json:
Here is the code for activating the exporting part:
...
child: FlatButton(
onPressed: () async{
//Popping the confirm dialog
Navigator.pop(context);
//Showing the progress dialog
showProcessingDialog();
//Buying some time
_timer = Timer(Duration(seconds: 5), exportData);
//Pops the progress dialog
Navigator.pop(context);
//Shows the finished dialog
showFinishedDialog();
},
child: Text(
"Yes",
...
After I click 'Yes' in this alert button, it should show the progress dialog but it doesn't show, instead it shows the finished dialog.
Like this:
Here is the code for progress dialog:
void showProcessingDialog() async{
return showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context){
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0))),
contentPadding: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 0.0),
content: Container(
width: 250.0,
height: 100.0,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
CircularProgressIndicator(),
Text("Exporting...",
style: TextStyle(
fontFamily: "OpenSans",
color: Color(0xFF5B6978)
)
)
]
)
)
);
}
);
}
Here is the exportData callback:
void exportData() async{
List<dynamic> _msgList = await _msgStorage._getList;
await _expData._saveList(_msgList);
}
I have tried to add Timer class to delay showing finished dialog for 3 seconds but it doesn't work. I can confirm that my json file was exported successfully but the callback of Timer which is the progress dialog didn't show up.
I would appreciate any kind of help.
UPDATE:
I rewrote my code based on the answer of diegoveloper:
onPressed: () async{
Navigator.pop(context);
print("confirm dialog has pop");
print("showing processdialog");
showProcessingDialog();
print("processdialog is being shown.");
print("buying some time");
await Future.delayed(Duration(seconds: 5));
print("done buying some time");
print("exporting begin");
await exportData();
print("exporting done");
Navigator.pop(context);
print("processdialog has pop");
print("showing finished dialog");
showFinishedDialog();
print("finished dialog is being shown.");
},
At this point, the process dialog is being shown but after printing the "exporting done" and executing the Navigator.pop(context);
it gave an error and the process dialog remains in the screen, unpopped.
Like this:
I/flutter ( 9767): confirm dialog has pop
I/flutter ( 9767): showing processdialog
I/flutter ( 9767): processdialog is being shown.
I/flutter ( 9767): buying some time
I/flutter ( 9767): done buying some time
I/flutter ( 9767): exporting begin
I/flutter ( 9767): exporting done
E/flutter ( 9767): [ERROR:flutter/shell/common/shell.cc(184)] Dart Error: Unhandled exception:
E/flutter ( 9767): Looking up a deactivated widget's ancestor is unsafe.
E/flutter ( 9767): 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 inheritFromWidgetOfExactType() in the widget's didChangeDependencies() method.
After I comment out the await Future.delayed(Duration(seconds: 5));
it worked fine.
My question is why did it failed when using Future.delayed
?
Here is the full error:
I/flutter ( 9767): confirm dialog has pop
I/flutter ( 9767): showing processingdialog
I/flutter ( 9767): processdialog is being shown.
I/flutter ( 9767): buying some time
I/flutter ( 9767): done buying some time
I/flutter ( 9767): exporting begin
I/flutter ( 9767): exporting done
E/flutter ( 9767): [ERROR:flutter/shell/common/shell.cc(184)] Dart Error: Unhandled exception:
E/flutter ( 9767): Looking up a deactivated widget's ancestor is unsafe.
E/flutter ( 9767): 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 inheritFromWidgetOfExactType() in the widget's didChangeDependencies() method.
E/flutter ( 9767):
E/flutter ( 9767): #0 Element._debugCheckStateIsActiveForAncestorLookup.<anonymous closure> (package:flutter/src/widgets/framework.dart:3246:9)
E/flutter ( 9767): #1 Element._debugCheckStateIsActiveForAncestorLookup (package:flutter/src/widgets/framework.dart:3255:6)
E/flutter ( 9767): #2 Element.ancestorStateOfType (package:flutter/src/widgets/framework.dart:3303:12)
E/flutter ( 9767): #3 Navigator.of (package:flutter/src/widgets/navigator.dart:1288:19)
E/flutter ( 9767): #4 ChatWindow.showExportedDialog.<anonymous closure>.<anonymous closure> (package:msgdiary/main.dart:368:37)
E/flutter ( 9767): <asynchronous suspension>
E/flutter ( 9767): #5 _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:507:14)
E/flutter ( 9767): #6 _InkResponseState.build.<anonymous closure> (package:flutter/src/material/ink_well.dart:562:30)
E/flutter ( 9767): #7 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:102:24)
E/flutter ( 9767): #8 TapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:242:9)
E/flutter ( 9767): #9 TapGestureRecognizer.handlePrimaryPointer (package:flutter/src/gestures/tap.dart:175:7)
E/flutter ( 9767): #10 PrimaryPointerGestureRecognizer.handleEvent (package:flutter/src/gestures/recognizer.dart:315:9)
E/flutter ( 9767): #11 PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:73:12)
E/flutter ( 9767): #12 PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:101:11)
E/flutter ( 9767): #13 _WidgetsFlutterBinding&BindingBase&GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:180:19)
E/flutter ( 9767): #14 _WidgetsFlutterBinding&BindingBase&GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:158:22)
E/flutter ( 9767): #15 _WidgetsFlutterBinding&BindingBase&GestureBinding._handlePointerEvent (package:flutter/src/gestures/binding.dart:138:7)
E/flutter ( 9767): #16 _WidgetsFlutterBinding&BindingBase&GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:101:7)
E/flutter ( 9767): #17 _WidgetsFlutterBinding&BindingBase&GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:85:7)
E/flutter ( 9767): #18 _invoke1 (dart:ui/hooks.dart:168:13)
E/flutter ( 9767): #19 _dispatchPointerDataPacket (dart:ui/hooks.dart:122:5)
UPDATE:
It was my fault. I need to study more about context. It seems that I was popping the same context for the two dialogs. I changed the name of the dialog and it worked.
Upvotes: 2
Views: 12257
Reputation: 17736
Why don't you extract it to a custom dialog widget and handle its states dynamically? It's cleaner and more customizable, also giving a timer (like you did of 5 seconds) it's not a good practice since you can't be sure how much time it will take to do its work.
Then I can suggest, for example, to create an enum DialogState
with 3 states
enum DialogState {
LOADING,
COMPLETED,
DISMISSED,
}
Then create your own Dialog widget that when built receives its current state
class MyDialog extends StatelessWidget {
final DialogState state;
MyDialog({this.state});
@override
Widget build(BuildContext context) {
return state == DialogState.DISMISSED
? Container()
: AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
),
content: Container(
width: 250.0,
height: 100.0,
child: state == DialogState.LOADING
? Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
Padding(
padding: const EdgeInsets.only(left: 10.0),
child: Text(
"Exporting...",
style: TextStyle(
fontFamily: "OpenSans",
color: Color(0xFF5B6978),
),
),
)
],
)
: Center(
child: Text('Data loaded with success'),
),
),
);
}
}
and then, in your screen, you can insert it anywhere you want. I changed my exportData
function to dummy a request that takes 5 seconds.
class MyScreen extends StatefulWidget {
_MyScreenState createState() => _MyScreenState();
}
class _MyScreenState extends State<MyScreen> {
DialogState _dialogState = DialogState.DISMISSED;
void _exportData() {
setState(() => _dialogState = DialogState.LOADING);
Future.delayed(Duration(seconds: 5)).then((_) {
setState(() => _dialogState = DialogState.COMPLETED);
Timer(Duration(seconds: 3), () => setState(() => _dialogState = DialogState.DISMISSED));
});
}
@override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Stack(
alignment: Alignment.center,
children: <Widget>[
RaisedButton(
child: Text('Show dialog'),
onPressed: () => _exportData(),
),
MyDialog(
state: _dialogState,
)
],
),
),
);
}
}
Upvotes: 3
Reputation: 103351
You can do the following:
onPressed: () async{
//Popping the confirm dialog
Navigator.pop(context);
//Showing the progress dialog
showProcessingDialog();
//wait 5 seconds : just for testing purposes, you don't need to wait in a real scenario
await Future.delayed(Duration(seconds: 5));
//call export data
await exportData();
//Pops the progress dialog
Navigator.pop(context);
//Shows the finished dialog
await showFinishedDialog();
},
Upvotes: 1