Mister disco
Mister disco

Reputation: 165

How to fix my code to show alert dialog on flutter?

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.

Upvotes: 2

Views: 12257

Answers (2)

Miguel Ruivo
Miguel Ruivo

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.

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

Upvotes: 3

diegoveloper
diegoveloper

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

Related Questions