基于流的颤振小吃店展示
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));
【讨论】:
以上是关于基于流的颤振小吃店展示的主要内容,如果未能解决你的问题,请参考以下文章