基于流的颤振小吃店展示

Posted

技术标签:

【中文标题】基于流的颤振小吃店展示【英文标题】:flutter snackbar display based on stream 【发布时间】:2019-05-30 02:19:03 【问题描述】:

我目前有一个有趣的问题,与基于用户操作显示快餐栏有关。

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

我有 2 个屏幕:

    员工名单 添加员工

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

这就是我想要的:

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

我尝试了几种实现方式:

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

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

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

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

(将根据要求发布代码)

感谢您的帮助!

【问题讨论】:

【参考方案1】:

有现成的配方可以在Bloc 中显示Snackbar。 基本上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()
      ),
   );
  

【讨论】:

关注这个答案:***.com/a/65906839/1374532【参考方案2】:

Builder 包裹你的body(你的Scaffold 的孩子,在你的情况下由_buildBody 返回),这样你就可以用context 编写一些“免费”代码。

现在您可以使用您提供的处理程序来监听流(就像您已经做过的那样),但是这次您在每个 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
  

【讨论】:

问题是有多个路由在监听同一个事件,Bloc模式没有解决这个问题看我的回复【参考方案3】:

@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 上侦听流(如果您使用的是 blocprovider,则在 initState 或 didChangeDependencies 上)。

【讨论】:

哦,一个有趣的方法。我遇到的唯一问题是您将 UI 逻辑与您的集团混合在一起。如果你想在一个 Angular 应用程序中重用你的业务逻辑,这个 bloc 会出错,因为 ScaffoldState 不存在。不过我确实喜欢这个主意,谢谢你的洞察力!【参考方案4】:

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

创建一个新的流(在我的例子中是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 进行侦听。

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

【讨论】:

可能会导致问题(内存泄漏?),因为每次 !hasData 通过时,都会添加一个新的侦听器【参考方案5】:

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

问题

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

解决方案

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

...
  @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();
  
...

【讨论】:

【参考方案6】:

你可以在builder里面试试这个:

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

【讨论】:

以上是关于基于流的颤振小吃店展示的主要内容,如果未能解决你的问题,请参考以下文章

基于log4j的消息流的实现之一消息获取

Flutter Redux 小吃店

你如何测试一个消耗流的颤振块

如何在颤振中基于同一个流控制器获取多个流?

NSJSONSerialization 与基于流的解析器

使用C#处理基于比特流的数据