2

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:

enter image description here

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:

enter image description here

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:

enter image description here

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.

asked Dec 20, 2018 at 15:59
1
  • 1
    You should replace the widget within your dialog for a progress bar/circle and update it back again when the work is completed. Commented Dec 20, 2018 at 16:09

2 Answers 2

3

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.

enter image description here

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,
 )
 ],
 ),
 ),
 );
 }
}
answered Dec 20, 2018 at 16:51
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for the advice. However, I'm worried if I call the setState method, it would rebuild my widget tree. I have a streambuilder widget which listens and displays the list of messages onto my Screen State.
You can also do this with stream builders an listen to changes on your dialog. When you want to update the message you just need to add to your stream the news state. Also, remember that setState will only rebuild the widgets that contains changes.
1

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();
 },
answered Dec 20, 2018 at 16:14

2 Comments

I tried to follow your suggestion and it showed the process dialog. However, there is a tiny error. I updated the post for that. Thanks.
Don't mind my first comment. It was my fault for using the same name for the contexts.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.