Flutter:Bloc,如何显示警报对话框

24

我在了解块模式和流处理方面还比较新,我想在按下按钮时显示一个警示对话框,但是我找不到实现的方法。目前我的代码如下:

Widget button() {
  return RaisedButton(
      child: Text('Show alert'),
      color: Colors.blue[700],
      textColor: Colors.white,
      onPressed: () {
        bloc.submit();
      });
   }



return Scaffold(
        appBar: AppBar(
          title: Text("Title"),
        ),
        body: StreamBuilder(
            stream: bloc.getAlert,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return Text("I have Dataaaaaa ${snapshot.data}");
              } else
                return ListView(
                  children: <Widget>[
                    Container(
                     button()
                    )
                ...

还有BLoC:

final _submissionController = StreamController();
Stream get submissionStream=> _submissionController.stream;
Sink get submissionSink=> _submissionController.sink;

我尝试做类似这样的事情:

Widget button() {
  return StreamBuilder(
stream: submissionStream
builder: (context, snapshot){
if (snapshot.hasData){
return showDialog(...)
}else
 return RaisedButton(
      child: Text('Show alert'),
      color: Colors.blue[700],
      textColor: Colors.white,
      onPressed: () {
        bloc.submit();
      });
   }

但是,当然,这并没有起作用。

7个回答

31
在构建工作时不能显示对话框。当您有新数据时,然后创建一个新的小部件。在这种情况下,您最好不使用流,但如果必要,则应该使用

WidgetsBinding.instance.addPostFrameCallback((_) => yourFunction(context));

或者

Future.microtask(() => showDialogFunction(context));

在您的 if 语句中

if (snapshot.hasData) {   WidgetsBinding.instance.addPostFrameCallback((_) => showDialogFunction(context)); }

此代码将在 build 方法之后启动,因此对话框将立即显示。

Bloc 函数始终返回小部件,因此当流具有数据时始终返回 button() 或不同的小部件。


是的,在这些情况下最好避免使用BLoC,但由于我对它还很陌生,我仍然需要学习如何正确地使用它。不过你建议的方法行得通,所以谢谢你! - Little Monkey

11

你可以使用 flutter_bloc 中的 BlocListener 或 BlocConsumer 来显示对话框、Snackbar 或导航到新页面。

采用这种方法,你可能希望重构代码,依赖于 bloc 状态而不是直接访问流。

每次状态变化时,Listener 只会被调用一次,但 builder 可能会被多次调用。此外,你不能在 builder 上执行某些操作,例如导航到另一个页面。

return Scaffold(
  appBar: AppBar(
    title: Text("Title"),
  ),
  body: BlocProvider<YourBloc>(
    create: () => YourBloc(),
    child: Stack([
      SnackbarManager(),
      YourScreen(),
    ]),
  ),
);
...

/// This is basically an empty UI widget that only
/// manages the snackbar
class SnackbarManager extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocListener<YourBloc, YourBlocState>(
      listener: (context, state) {
        if (state.hasMyData) {
          Scaffold.of(context).showSnackBar(SnackBar(
            content:
                Text("I got data"),
          ));
        }
      },
      child: Container(),
    );
  }
}

注意:BlocConsumer是BlocBuilder和BlocListener的组合,允许您在同一个小部件中使用监听器和构建器。

我正在尝试使用bloc在对话框中显示未来数据,但我不明白当您使用stack[SnackBarManager(), screen()]时的意思。在这种情况下,堆栈是否允许屏幕和块侦听器同时运行? - Nefarious
@Nefarious 我认为那只是我当时的设置,只是一个例子。你只需要在 Bloc 中使用监听器,比如 BlocListener、BlocConsumer。在捕获到状态改变之后,由你的实现来显示对话框。可以是一个服务,一些你正在使用的类等。 - mirkancal

3

我知道我来晚了,但也许这会帮助到某些人。 我现在正在学习BLoC,并遇到了类似的问题。

首先,我想推荐从pub.dev下载 flutter_bloc包。 它包含帮助您解决此类问题的小部件,例如BlocListenerBlocConsumer

如果你不想使用它,可以尝试使用StatefulWidget,并单独listen,然后使用逻辑来显示对话框(还要确保您的流正在广播,就像我的示例一样,因此它可以有多个监听器)

我做了一个示例,你可以将其复制到dartpad.dev/flutter中:

import 'dart:async';
import 'package:flutter/material.dart';

final myStream = StreamController<bool>.broadcast();

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark(),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: MyWidget(),
        ),
      ),
    );
  }
}

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {

  initState() {
    super.initState();
    myStream.stream.listen((show){
      if(show)
        showDialog(
        barrierDismissible: false,
        context: context,
        builder: (context) {
          return AlertDialog(
            title: Text('MyDialog'),
            actions: [
              TextButton(
                child: Text('Close'),
                onPressed: (){
                  myStream.sink.add(false);
              }),
            ]
          );
        }
      );
      if(!show) {
        Navigator.pop(context);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Center(child: ElevatedButton(
      child: Text('Show Alert'),
    onPressed: (){
      myStream.sink.add(true);
    }));
  }
}

它与我的答案有何不同?https://dev59.com/nlQJ5IYBdhLWcg3wqnsT#63127054 - mirkancal
你正在展示一个Snackbar,而不是一个Dialog。 Dialog会持续存在,这使得它比Snackbar更棘手一些,因为Snackbar会在一段时间后消失。 - SEG.Veenstra

1

以下是我所做的,可能有误,因为我也是Flutter的新手。但适用于我的情况。

Widget build(BuildContext context) {
final authBloc = BlocProvider.of<AuthBloc>(context);

authBloc.outServerResponse.listen((serverResponse) {
  if (serverResponse.status == 'success') {
    _navigateToLogin();
  } else {
    _showSnakBar(serverResponse.message);
  }
});
.... Rest of the code which returns the widget, 
which in my case is form widget with button for submitting as follows,
onPressed: () {
  if (_formKey.currentState.validate()) {
      _formKey.currentState.save();
      authBloc.processRegister.add(_registrationData.toMap());
  }
}

outServerResponse是API POST调用完成后输出的流。

authBloc.processRegister是将表单数据传递给我的Auth API服务提供程序的输入接收器。

_nagivateToLogin和_showSnakBar是简单的函数。

_navigateToLogin() {
      Navigator.of(context).pop();
}

_showSnakBar(String msg) {
     Scaffold.of(context).showSnackBar(
      SnackBar(
        content: Text(msg),
      ),
     );
 }

2
似乎每次构建时,流都会被重新订阅,使用'authBloc.outServerResponse.listen'。我认为订阅必须关闭。 - Ride Sun
@RideSun true。如果它是有状态的并且在dispose时关闭,最好将其放在initstate中。 - blisssan

0
如果您正在使用我建议使用的flutter_bloc包,您应该使用提供的BlocListener小部件来监听状态更改并执行逻辑代码。例如:
BlocListener<BlocA, BlocAState>(
    listener: (context, state) {
    // do stuff here based on BlocA's state
    },
    child: Container(),
);

但是如果您还需要构建小部件,您应该使用BlocConsumer小部件,它同时具有监听器和构建器:

BlocConsumer<BlocA, BlocAState>(
    listener: (context, state) {
    // do stuff here based on BlocA's state
     },
    builder: (context, state) {
    // return widget here based on BlocA's state
     }
 );

通常在不更改构建小部件的情况下显示对话框很常见,因此BlocConsumer为此情况提供了buildWhen选项,该选项采用先前和当前状态来决定生成器。
buildWhen: (state, currentState){
                  if (state is MainComplexTableState && currentState is NewComplexRegistration) {
                    return false;
                  }
                  if (state is ErrorToShowUp) {
                    return false;
                  }
                  return true;
                },

0

这个过程对我很有效。在返回小部件之前,我调用了我的对话框。

Future.microtask(() => showLoginSuccess(BuildContext context));


-1
我通过维护两个上下文来解决了这个问题
BlocProvider of type A  ==>widget class B(showdialog(context:context,builder(context2){
Blocprvider.value(value:Blocprovider.of<A>.context)
child:BlocListener(
listner(context2,state)
{// 
 your works
//}
child:AlertDialog( some widgets
a button function ()=> context.read<A>().function or property name
//

1. 这里我们所谓的旧上下文实际上是已经在提供者中注册了的,2. context2 仅用于构建新的构建器小部件。 3. 因此,我们可以通过导航传递 bloc 并在导航警报小部件中访问它,而无需创建它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接