Ad

Refresh StatefulBuilder Dialog Without Using OnPressed

- 1 answer

I need to update the text of my dialog while my report is loading. setState doest not work here.

class ReportW extends StatefulWidget {
  const ReportW({Key key}) : super(key: key);

  @override
  _ReportWState createState() => _ReportWState();
}

class _ReportWState extends State<ReportMenuDownloadW> {

  String loadingText;

  void updateLoadingText(text){
    setState(() {loadingText = text;});
  }

  @override
  Widget build(BuildContext context) {

    return MyWidget(
      label:REPORT_LABEL,
      onTap: () async {
        showDialog(context: context,
            builder: (BuildContext context) {
              return StatefulBuilder(
                  builder: (context, setState) {
                    return Dialog(
                      child: Column(
                        children: [
                          CircularProgressIndicator(),
                          Text(loadingText),
                        ],
                      ),
                    );});
            });
        await loadPDF(context,updateLoadingText);
        Navigator.pop(context);
      },
    );
  }
}

Is there an alternative solution if it is not possible ? I just need a progress text indicator over my screen while loading.

Ad

Answer

In your case you can use GlobalKey. For your code:

  1. Define globalKey inside your widget:
// Global key for dialog
final GlobalKey _dialogKey = GlobalKey();
  1. Set globalKey for your StatefulBuilder:
return StatefulBuilder(
  key: _dialogKey,
  builder: (context, setState) {
    return Dialog(
      child: Column(
        children: [
          CircularProgressIndicator(),
          Text(loadingText),
        ],
      ),
    );
  },
);
  1. Now you can update UI of your dialog like this:
void updateLoadingText(text) {
  // Check if dialog displayed, we can't call setState when dialog not displayed
  if (_dialogKey.currentState != null && _dialogKey.currentState!.mounted) {
    _dialogKey.currentState!.setState(() {
      loadingText = text;
    });
  }
}

Pay attention, you get unexpected behavior if user will close dialog manually.

How to prevent closing dialog by user: in showDialog use barrierDismissible: false and also wrap your dialog to WillPopScope with onWillPop: () async {return false;}

Possible question: Why we check _dialogKey.currentState != null? Because opening dialog and set globalKey take some time and while it's not opened currentState is null. If updateLoadingText will be call before dialog will be open, we shouldn't update UI for dialog.

Full code of your widget:

class OriginalHomePage extends StatefulWidget {
  OriginalHomePage({Key? key}) : super(key: key);

  @override
  _OriginalHomePageState createState() => _OriginalHomePageState();
}

class _OriginalHomePageState extends State<OriginalHomePage> {
  String loadingText = "Start";

  // Global key for dialog
  final GlobalKey _dialogKey = GlobalKey();

  void updateLoadingText(text) {
    // Check if dialog displayed, we can't call setState when dialog not displayed
    if (_dialogKey.currentState != null && _dialogKey.currentState!.mounted) {
      _dialogKey.currentState!.setState(() {
        loadingText = text;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () async {
        showDialog(
          context: context,
          builder: (BuildContext context) {
            return StatefulBuilder(
              key: _dialogKey,
              builder: (context, setState) {
                return Dialog(
                  child: Column(
                    children: [
                      CircularProgressIndicator(),
                      Text(loadingText),
                    ],
                  ),
                );
              },
            );
          },
        );
        await loadPDF(context, updateLoadingText);
        Navigator.pop(context);
      },
      child: Text("Open"),
    );
  }
}

Also i rewrote your code a bit, it seems to me more correct:

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ElevatedButton(
          child: Text("Open"),
          onPressed: () => _showDialog(),
        ),
      ),
    );
  }

  // Global key for dialog
  final GlobalKey _dialogKey = GlobalKey();

  // Text for update in dialog
  String _loadingText = "Start";

  _showDialog() async {
    showDialog(
      context: context,
      barrierDismissible: false,
      builder: (BuildContext context) {
        return WillPopScope(
          onWillPop: () async {
            return false;
          },
          child: StatefulBuilder(
            key: _dialogKey,
            builder: (context, setState) {
              return Dialog(
                child: Padding(
                  padding: EdgeInsets.all(8),
                  child: Column(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      CircularProgressIndicator(),
                      Text(_loadingText),
                    ],
                  ),
                ),
              );
            },
          ),
        );
      },
    );

    // Call some function from service
    await myLoadPDF(context, _setStateDialog);

    // Close dialog
    Navigator.pop(context);
  }

  // Update dialog
  _setStateDialog(String newText) {
    // Check if dialog displayed, we can't call setState when dialog not displayed
    if (_dialogKey.currentState != null && _dialogKey.currentState!.mounted) {
      _dialogKey.currentState!.setState(() {
        _loadingText = newText;
      });
    }
  }
}

Result:

Updated dialog

Ad
source: stackoverflow.com
Ad