19

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

 showDialog(... builder: (context) => MyDialog);

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

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

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

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

4

9 回答 9

11

您可以使用 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

于 2020-06-17T17:23:10.943 回答
9

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

这更有意义

  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}');
 }
}
于 2020-11-30T06:27:07.397 回答
1

如果这对您来说是一个选择,只需将提供程序提升到上方即可MaterialApp。对于全球唯一的提供者来说,这可能是一个很好的解决方案,例如用户配置或类似的: 在此处输入图像描述

于 2020-11-19T10:50:02.407 回答
1

尝试这个。创建一个不同的有状态小部件来容纳对话框,并在调用 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();
  }


}
于 2019-11-27T13:39:23.123 回答
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();
  }
}
于 2019-11-01T17:02:15.060 回答
0

我发现从对话框中访问 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();
      },
    );
  }
}
于 2021-09-21T05:50:47.207 回答
0

您必须将提供的内容直接传递给对话框构造函数,才能在对话框的新上下文中访问它。如果您在对话框中有一个非常深的小部件树并且您想从更深的地方访问它,您也可以将它提供给对话框树顶部的新 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,
                  )
于 2019-11-01T18:10:09.443 回答
0

我今天遇到了同样的问题,我能够通过将对话框包装在 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();
                                      });
                                    },
                                  ),
                                ),
                              ),
                            ),
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ),
            ),
          );
        });
      });
于 2020-07-07T15:55:14.840 回答
-1

发现这个有点晚了,但是遇到了同样的挑战并实现了一个解决方案:您需要在 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);
                    },

等等

于 2020-08-12T06:09:37.300 回答