【问题标题】:How to access Provider providers in Dialogs in Flutter如何在 Flutter 的 Dialogs 中访问 Provider 提供者
【发布时间】:2020-01-17 23:25:18
【问题描述】:

Provider 包使用InheritedWidget。当我想在对话框中访问提供者时,这是一个问题。如果我使用

加载对话框
 showDialog(... builder: (context) => MyDialog);

我无法使用InheritedWidget 访问任何内容,因为我的对话框不是主窗口小部件树的一部分。这也意味着我无法访问我的 Provider 提供商,对吗?

我的问题是:如果我的提供程序不是主应用小部件树的一部分,我如何在对话框中访问它?

final firebaseAuth = Provider.of<FirebaseAuth>(context);

我在使用BLoCs 时遇到了同样的问题。如果我尝试通过InheritedWidget 在对话框中检索它们,它们会失败。我已经通过在构造函数中传递BLoC 来解决这个问题,但这似乎违背了InheritedWidgets 的目的。

【问题讨论】:

  • 不是解决方案,而是一个建议:使用 BLoC 模式,使用依赖注入将 BLoC 注入组件是很常见的。在这种情况下,您不依赖小部件树来接收实例。我个人建议 DI 使用 getIt
  • 我已经尝试过了,我喜欢它。但我已经看到 Provider 包几乎可以满足您的所有需求。唯一的问题是整个小部件树。我希望有一种巧妙的方式在我的应用程序中使用“Provider”,而不必求助于 getIt。但我同意,这绝对是一个解决方案。

标签: flutter flutter-provider inherited-widget


【解决方案1】:

我能够通过将数据集传递到警报对话框来访问 Provider 数据。有趣的是,您必须在 Dialog 中调用 setState() 才能看到 Dialog 中的更改。

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {

    final provider = Provider.of<DataSet>(context);

    return Scaffold(
      body: Container(
        child: RaisedButton(
        child: Text('Show Dialog'),
          onPressed: () {
            showDialog(context: context,
            builder: (context) {
              return DialogContent(dataSet: provider);
            });
          },
        ),
      ),
    );
  }
}

class DialogContent extends StatefulWidget {

  final DataSet dataSet;

  const DialogContent({Key key, this.dataSet}) : super(key: key);

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

class _DialogContentState extends State<DialogContent> {
  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: Text('Dialog with data'),
      content: Text('${widget.dataSet.pieceOfData}'),
      actions: <Widget>[
        FlatButton(
          child: Text('Increase Data'),
          onPressed: () {
            setState(() {
              widget.dataSet.increaseData();
            });
          },
        ),
      ],
    );
  }
}

class DataSet with ChangeNotifier {
  int pieceOfData = 1;

  increaseData() {
    pieceOfData += 1;
    notifyListeners();
  }
}

【讨论】:

  • 在调用 Provider.of(context) 时不会报错吗?我没有看到您在哪里使用 ChangeNotifierProvider...
  • @Gpack 很抱歉没有在我的帖子中包含这些内容。我发现,在大多数情况下,当我在可能的最高级别实现 Provider 包时,它的效果最好。所以,我返回 ChangeNotifierProvider 作为 MyApp 的 build 方法中的第一个 Widget
  • void main() => runApp(MyApp()); class MyApp extends StatelessWidget { // 这个小部件是应用程序的根。 @override Widget build(BuildContext context) { return ChangeNotifierProvider( builder: (context) => BabyInfo(), child: MaterialApp( debugShowCheckedModeBanner: false, initialRoute: '/', routes: { '/': (context) => MyHomePage(), '/settings': (context) => CalorieSettings(), }, theme: ThemeData(primarySwatch: Colors.lightBlue, ), ), ); } }
【解决方案2】:

您必须将提供的东西直接传递给对话框构造函数才能在对话框的新上下文中访问它。如果您在对话框中有一个非常深的小部件树并且您想从更深的地方访问它,您也可以将它提供给对话框树顶部的新 Provider 小部件。

如果您使用 Bloc,通常您会告诉 Provider 在处理提供程序小部件以清理流控制器/订阅时调用 Bloc 的 dispose 方法。显然,如果您将 bloc 重新提供给对话框,或者如果此 bloc 在对话框之外使用,您可能不想这样做。

在对话框中使用有状态或无状态小部件由您决定,只要您有权访问该块,您就可以使用流构建器并照常收听某些流。

一个例子:

class EditEventDialog extends StatelessWidget {

  final GroupBloc groupBloc;

  EditEventDialog({this.groupBloc})
      : assert(groupBloc != null);

  @override
  Widget build(BuildContext context) {
    return Provider(
      builder: (context) => groupBloc,
      child: Dialog(
        child: Container(
          height: 400.0,
          width: 200.0,
          child: StreamBuilder<StatusStreamType>(
            stream: groupBloc.statusStream,
            builder: (context, snapshot) {
    ....

并称之为:

onPressed: () => showDialog(
                    builder: (newContext) {
                      GroupBloc groupBloc = Provider.of<GroupBloc>(context);
                      return EditEventDialog(
                        groupBloc: groupBloc,
                      );
                    },
                    context: context,
                  )

【讨论】:

    【解决方案3】:

    试试这个。创建一个不同的有状态小部件来容纳对话框,并在调用 showDialog() 方法时返回该对话框有状态小部件。下面的例子

    class MainScreen extends StatefulWidget {
      @override
      _MainScreenState createState() => _MainScreenState();
    }
    
    class _MainScreenState extends State<MainScreen> {
    
      @override
      void initState() {
        super.initState();
    
      }
    
      @override
      void dispose() {
        super.dispose();
      }   
    
      @override
      Widget build((BuildContext context) {
        MainProvider mainProvider = MainProvider.of(context);
    
        return Scaffold(
            appBar: AppBar(
                elevation: 0,
                backgroundColor: Colors.white,
            ),
            body: Center(
                child: Container(
                    child: RaisedButton(
                        onPressed: ()=> _openBottomSheet(context, mainProvider),
                        child: Text("Open Dialog"),
                    )
                )
            )
        );
    }
    
    _openBottomSheet(BuildContext context, MainProvider mainProvider) async {
        await showModalBottomSheet<bool>(
            context: cntxt,
            builder: (_) {
                return BottomSheetDialog();
            }
        );
    }
    
    }
    
    class BottomSheetDialog extends StatefulWidget {
      @override
      _BottomSheetDialogState createState() => _BottomSheetDialogState();
    }
    
    class _BottomSheetDialogState extends State<BottomSheetDialog> {
    
       @override
       void initState() {
         super.initState();
    
       }
    
       @override
       void dispose() {
        super.dispose();
       }
    
       @override
       Widget build(BuildContext context) {
          MainProvider mainProvider = MainProvider.of(context);
    
          return Container(
            width: MediaQuery.of(context).size.width,
            height:MediaQuery.of(context).size.height/2.2,
            margin: EdgeInsets.fromLTRB(16,16,16,0),
            decoration: BoxDecoration(
                color: mainProvider.color,
                borderRadius: BorderRadius.only(
                    topLeft: Radius.circular(20),
                    topRight: Radius.circular(20),
                ),
            ),
            child: RaisedButton(    
                onPressed: ()=> mainProvider.changeColor(),
                child: Text("Open Dialog"),
            )
        )
    
    }
    
    }
    
    
    class MainProvider with ChangeNotifier {
    
      static MainProvider of(BuildContext context) {
        return Provider.of<MainProvider>(context);
      }
    
      Color _color = Colors.white;
      bool _isColorChanged = false;
    
      Color get color => _color;
      bool get isColorChanged => _isColorChanged;
    
      changeColor() {
        if(!isColorChanged) {
            _color = Colors.green;
        }else{
            _color = Colors.white;
        }
        _isColorChanged = !_isColorChanged;
    
        notifyListeners();
      }
    
    
    }
    

    【讨论】:

      【解决方案4】:

      您可以使用 BlocProvider.value,而不是在构造函数中传递 BLoC。

      https://pub.dev/documentation/flutter_bloc/latest/flutter_bloc/BlocProvider/BlocProvider.value.html

      这将允许您将现有的 BLoC 实例提供给您的新路由(对话框)。而且您仍然可以获得InheritedWidget 的所有好处

        // Get the BLoC using the provider
        MyBloc myBloc = BlocProvider.of<MyBloc>(context);
      
        showDialog(
          context: context,
          builder: (BuildContext context) {
            Widget dialog = SimpleDialog(
              children: <Widget>[
                ... // Now you can call BlocProvider.of<MyBloc>(context); and it will work
              ],
            );
      
            // Provide the existing BLoC instance to the new route (the dialog)
            return BlocProvider<MyBloc>.value(
              value: myBloc, //
              child: dialog,
            );
          },
        );
      

      .value() 也存在于 ChangeNotifierProvider、ListenableProvider 等。 https://pub.dev/documentation/provider/latest/provider/ChangeNotifierProvider/ChangeNotifierProvider.value.html

      https://pub.dev/documentation/provider/latest/provider/ListenableProvider/ListenableProvider.value.html

      【讨论】:

        【解决方案5】:

        我今天遇到了同样的问题,我能够通过将对话框包装在 Stateful Builder 中并在新的小部件树中设置状态来解决它。

              context: context,
              builder: (context) {
                return StatefulBuilder(builder: (context, setState) {
                  return Dialog(
                    child: SingleChildScrollView(
                      child: Container(
                        child: SingleChildScrollView(
                          child: Column(
                            children: <Widget>[
                              Padding(
                                padding: EdgeInsets.symmetric(vertical: height * .05),
                                child: Text('Choose An Avatar'),
                              ),
                              Stack(
                                children: <Widget>[
                                  Align(
                                    alignment: Alignment.center,
                                    child: CircleAvatar(
                                      minRadius: width * .09,
                                      maxRadius: width * .09,
                                      backgroundColor: Colors.brown,
                                      backgroundImage: AssetImage(
                                          'assets/profile${appData.avatar}.png'),
                                    ),
                                  ),
                                  Positioned.fill(
                                    left: width * .04,
                                    child: Align(
                                      alignment: Alignment.centerLeft,
                                      child: Container(
                                        width: width * .18,
                                        child: Material(
                                          color: Colors.transparent,
                                          child: InkWell(
                                            child: Icon(Icons.arrow_left,
                                                size: width * .18),
                                            onTap: () {
                                              setState(() {
                                                appData.changeAvatar();
                                              });
                                            },
                                          ),
                                        ),
                                      ),
                                    ),
                                  ),
                                ],
                              ),
                            ],
                          ),
                        ),
                      ),
                    ),
                  );
                });
              });
        

        【讨论】:

          【解决方案6】:

          发现这一点有点晚,但刚刚遇到同样的挑战并实现了解决方案:您需要在 showDialog 调用之外维护对上下文的引用。默认情况下,我们通常只使用“context”作为 showDialog 外部和内部的上下文名称,从而屏蔽了在 showDialog 中使用的外部上下文。因此,改为在 showDialog 中使用不同的名称(例如“c”),然后您仍然可以使用“final firebaseAuth = Provider.of(context);”在 showDialog 中,它会根据需要从主树中找到 FirebaseAuth 对象。

          这是我正在处理的一些代码的简短摘录:

          showDialog(
              context: context,
              builder: (c) {
                  final action = Provider.of<ActionType>(context);
                  final host = Provider.of<String>(context);
                  return AlertDialog(
                      title: Text('Action API'),
                      actions: [
                          FlatButton(
                              onPressed: () {
                                  Navigator.pop(c);
                              },
          

          等等

          【讨论】:

            【解决方案7】:

            如果您愿意,只需将提供程序提升到 MaterialApp 上方即可。对于全球唯一的提供商来说,这可能是一个很好的解决方案,例如用户配置或类似:

            【讨论】:

              【解决方案8】:

              我在这部分卡住了一段时间。老实说,我不想通过提供程序,当您处理复杂的小部件时,解包小部件代码以获取父上下文也很困难(而且这似乎不是最好的方法)。

              这更有意义

                handleFileViewerClicked(context) async {
                  var reportState = Provider.of<ReportState>(context, listen: false);
                  /**
                   *The dialog will live in a new context and requires a new provider to be created for the report state
                   * For more information read the Provider.Consumer documentation and showDialog function signature.
                   */
                  showDialog(
                    context: context,
                    //Notice the use of ChangeNotifierProvider<ReportState>.value
                    builder: (_) => ChangeNotifierProvider<ReportState>.value(
                      value: reportState,
                      child: FileViewer(),
                    ),
                  );
              }
              

              在这种情况下,您的子小部件(即 FileViewer)可以使用

              class FileViewer extends StatelessWidget {
              .
              .
              Widget build(BuildContext context) {
                  //you can enable or disable listen if you logic require so 
                  var reportState = Provider.of<ReportState>(context); 
                  return Text('${reportState.files.length}');
               }
              }
              

              【讨论】:

                【解决方案9】:

                我发现从对话框中访问 Bloc 提供程序的唯一方法是在 showDialog 调用之外定义对话框。

                class MyWidget extends StatelessWidget {
                  @override
                  Widget build(BuildContext context) {
                    return BlocConsumer<MyCubit, MyState>(
                      listener: (context, state) {
                        if (state.shouldShowDialog == true) {
                          final dialog = AlertDialog(
                            content: Text("Info");
                            actions: <Widget>[
                              TextButton(
                                child: const Text('Approve'),
                                onPressed: () => {
                                  context
                                    .read<MyCubit>()
                                    .handleDialogApproved();
                                  Navigator.of(context, rootNavigator: true).pop();
                                }
                              )
                            ],
                          );
                
                          showDialog<void>(
                            context: context,
                            builder: (BuildContext context) {
                              return dialog;
                            },
                          );
                        }
                      },
                      builder: (context, state) {
                        return Container();
                      },
                    );
                  }
                }
                

                【讨论】:

                  猜你喜欢
                  • 2020-03-22
                  • 2020-01-27
                  • 2020-08-31
                  • 1970-01-01
                  • 2021-11-29
                  • 1970-01-01
                  • 2020-01-06
                  • 1970-01-01
                  • 2021-02-20
                  相关资源
                  最近更新 更多