10

我目前有一个有趣的问题,与基于用户操作显示小吃店有关。

以上听起来很琐碎,但让我们详细说明:

我有 2 个屏幕:

  1. 员工名单
  2. 添加员工

该应用程序使用 bloc 模式(使用流/rxdart)。

这是我想要的:

  • 用户单击员工列表屏幕中的添加员工 FAB 按钮并导航到添加员工屏幕(工作正常)
  • 用户填写员工详细信息并点击保存
  • 保存后,员工被添加到流中,并更新了员工屏幕列表(工作正常)
  • 用户被导航回员工列表(工作正常)
  • 显示 Snackbar 说明已成功添加员工(这是问题所在)

我尝试了几种实现方式:

添加一个新流(employeeAdded),并且在将员工添加到员工流时,另外将布尔值推送到已添加的员工。

在员工列表中,添加一个新的流构建器,并在构建器逻辑中添加快餐栏。

这会带来各种各样的问题,例如试图在页面已经(重新)构建之前显示小吃栏,等等。

问题是双重的:对于这种情况,什么是好的用户体验实践,对于这个问题,什么是好的解决方案?

(将根据要求发布代码)

谢谢您的帮助!

4

6 回答 6

10

有现成的配方可供展示SnackbarBloc基本上BlocListener是为此目的而创建的。请查看https://felangel.github.io/bloc/#/recipesfluttershowsnackbar

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final dataBloc = BlocProvider.of<DataBloc>(context);
    return Scaffold(
      appBar: AppBar(title: Text('Home')),
      body: BlocListener(
        bloc: dataBloc,
        listener: (BuildContext context, DataState state) {
          if (state is Success) {
            Scaffold.of(context).showSnackBar(
              SnackBar(
                backgroundColor: Colors.green,
                content: Text('Success'),
              ),
            );
          }
        },
        child: YourChild()
      ),
   );
  }
}
于 2019-10-24T09:45:51.180 回答
2

用 a包裹你的(你body的孩子Scaffold,在你的情况下由._buildBodyBuildercontext

现在您可以使用您提供的处理程序来监听流(就像您已经做过的那样),但是这次您每次Scaffold构建只注册一次监听器(而不是在employeesAdded流中发生的每个事件时注册监听器)。

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Builder(
        builder: (BuildContext context) {
          bloc.employeesAdded.listen(
            (_) {
              Scaffold.of(context).showSnackBar(
                SnackBar(
                  content: Text('Employee added'),
                ), // SnackBar
              );
            },
          );

          return _buildBody(bloc);
        },
      ), // Builder
    ); // Scaffold
  }
于 2019-03-04T20:15:49.673 回答
2

@joey 我找到了一个解决方案,但我不确定它是否是最好的。我现在正在将我的应用程序更改为 bloc 模式,发生了与您相同的问题,所以我这样做了:

在我的 bloc 文件中,我为支架状态放置了一个控制器:

ScaffoldState scaffold;

StreamController<ScaffoldState> _scaffoldController = StreamController<ScaffoldState>();
StreamSink<ScaffoldState> get setScaffold => _scaffoldController.sink;

在 bloc 文件的构造函数中,我监听该流:

LoginBloc(){
    _scaffoldController.stream.listen((sc){ scaffold = sc;});
}

在小部件中,每当我想使用它时,我都会下沉脚手架状态:

onPressed:() {
   bloc.setScaffold.add(Scaffold.of(context));
}

那么,我可以像往常一样展示该集团的小吃店:

scaffold.showSnackBar(SnackBar(content: Text(hi world'),),);

更新

我认为分离 ui 和 bloc 的最佳方法是监听 ui 上的流(如果您使用的是 blocpprovider,则在 initState 或 didChangeDependencies 上)。

于 2019-01-09T09:26:47.013 回答
1

虽然这不是我最喜欢的答案,但我也不确定是否有更好的选择;这是我为未来的观察者做的临时修复:

创建一个新流(在我的例子中是employeeAdded),在添加员工时,还要在流中创建一个条目:

  final _employees = BehaviorSubject<List<Employee>>(seedValue: List<Employee>());
  final _employeeAdded = BehaviorSubject();

  // streams (out)
  Observable<List<Employee>> get employees => _employees.stream;
  Observable<dynamic> get employeesAdded => _employeeAdded.stream;

  addEmployee(Employee employee) async {
    final newList = <Employee>[]..addAll(_employees.value)..add(employee);
    await _employeeRepo.upsertEmployee(employee);
    _employees.add(newList);

    _employeeAdded.add(true);
    //FIXME: Save to db
  }

注意在employeeAdded 中没有seedValue,这是为了防止snackbar 在初始加载时显示。

在我的屏幕/页面中,我有一个脚手架;它的主体调用了另一个应该解释其余代码的方法:

 Widget _buildBody(EmployeeBloc bloc) {
    return StreamBuilder(
      stream: bloc.employees,
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          bloc.employeesAdded.listen(
            (_) => Scaffold.of(context).showSnackBar(
                  SnackBar(
                    content: Text('Employee added'),
                  ),
                ),
          );
          bloc.seedEmployees();
          return Center(
            child: Text("No employees"),
          );
        }
        return _buildList(bloc, snapshot.data);
      },
    );
  }

请注意使用 hasData if 对 bloc 进行侦听。

这暂时有效,但想知道是否有更简洁的示例。

于 2019-01-03T15:35:22.363 回答
0

我已经为此苦苦挣扎了 2 周,我怀疑对实际问题存在误解。

问题

有多个路由(页面)正在监听同一个广播事件,当用户以干净的方式向前和向后导航时,我们无法暂停和恢复这些事件。这种情况下的多条路线是由用户在他/她导航到新屏幕时引起的,旧屏幕在后台仍然可用并接收事件,但在SnackBar用户点击后退按钮之前无法动画显示。

解决方案

在 a 中使用以下内容显示SnackBar唯一在最顶部的路线(这将是用户正在查看的路线)StatefulWidget

...
  @override
  void initState() {
    _subscription = employeeStream.listen((event) {
      if (ModalRoute.of(context).isCurrent) { // Check the top most route
        _l.fine('Only showing message on top');
        Scaffold.of(context).showSnackBar(...);
      } else { // Ignore all other routes
        _l.fine('Ignoring event');
      }
    });
    super.initState();
  }
...
于 2020-08-16T13:14:49.213 回答
-2

您可以在构建器中尝试此操作:

WidgetsBinding.instance.addPostFrameCallback((_) => showSnackBar(message));

于 2019-10-03T15:53:24.113 回答