不使用 onPressed 刷新 StatefulBuilder 对话框

Refresh StatefulBuilder Dialog without using onPressed

我需要在加载报告时更新对话框的文本。 setState 在这里不起作用。

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

如果不可能,是否有替代解决方案?我只需要在加载时在屏幕上显示一个进度文本指示器。

在您的情况下,您可以使用 GlobalKey。对于您的代码:

  1. 在您的小部件中定义 globalKey:
// Global key for dialog
final GlobalKey _dialogKey = GlobalKey();
  1. 为您的 StatefulBuilder 设置 globalKey:
return StatefulBuilder(
  key: _dialogKey,
  builder: (context, setState) {
    return Dialog(
      child: Column(
        children: [
          CircularProgressIndicator(),
          Text(loadingText),
        ],
      ),
    );
  },
);
  1. 现在您可以像这样更新 UI 对话框:
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;
    });
  }
}

注意,如果用户手动关闭对话框,您会遇到意外行为。

如何防止用户关闭对话框:在 showDialog 中使用 barrierDismissible: false 并使用 onWillPop: () async {return false;}

将对话框包装到 WillPopScope

可能的问题: 为什么我们检查 _dialogKey.currentState != null? 因为打开对话框和设置 globalKey 需要一些时间,而当它没有打开时,currentState 为 null。如果 updateLoadingText 将在对话框打开之前被调用,我们不应该为对话框更新 UI。

您的小部件的完整代码:

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

我也稍微重写了你的代码,在我看来更正确:

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

结果:

Updated dialog