Flutter BloC模式+提供程序+测试+记住状态

本文源于出版物“ 带有简单示例的BLoC模式 ”,其中我们弄清楚了该模式是什么以及如何在经典的简单反例中应用它。


根据评论并出于我的最佳理解,我决定尝试编写一个将收到问题答案的应用程序:


  1. 如何在整个应用程序中传递BloC所在的类的状态
  2. 如何为此模式编写测试
  3. (其他问题)如何在应用程序启动之间保持BLoC模式下的数据状态

下面是结果示例的animashka,并且在摘要下是一个汇报:)


在本文的结尾,一个有趣的问题是如何修改应用程序以从ReactiveX模式中应用Debounce运算符 (更确切地说,reactX是Observer模式的扩展)




应用程序说明和基本代码


与BLoC和提供商无关


  1. 该应用程序具有按钮+-,并通过滑动来复制这些按钮
  2. 通过内置的Flutter Mixin完成动画-TickerProviderStateMixin

链接到BLoC和提供商


  1. 两个屏幕-在第一个屏幕上滑动,在第二个屏幕上显示计数器更改
  2. 我们将状态写入手机的永久存储区(iOS和Android,程序包https://pub.dev/packages/shared_preferences
  3. 从持久性存储中写入和读取信息是异步的,我们也通过BLoC实现

我们正在编写一个应用程序


根据BLoC模式定义,我们的任务是从小部件中删除所有逻辑,并通过所有输入和输出均为Streams的类来处理数据。


同时,由于BLoC所在的类在不同的屏幕上使用,因此我们需要在整个应用程序中传输从此类创建的对象。


有不同的方法,即:


  1. 通过类构造函数,即所谓的提升状态 。 我们不会使用它,因为事实证明它很混乱,因此不要跟踪状态转移。
  2. 从我们拥有BLoC单身人士的班级中进行制作,然后将其导入到我们需要的地方。 它既简单又方便,但是,从我个人的角度来看,它使类构造函数变得复杂,并使逻辑有些混乱。
  3. 使用Provider程序包-Flutter团队推荐用于状态管理。 观看视频

在这个例子中,我们将使用Provider-来举例说明所有没有足够强度的方法:)


一般结构


所以我们有一堂课


class SwipesBloc { // some stuff } 

为了在整个窗口小部件树中访问从此类创建的对象,我们在应用程序窗口小部件的特定级别上定义了此类的提供者。 我是在小部件树的顶部完成此操作的,但是最好在最低的级别进行。


 class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MultiProvider( providers: [ Provider<SwipesBloc>(create: (_) => SwipesBloc()), ], child: MaterialApp( title: 'Swipe BLoC + Provider', 

在将这种漂亮的设计添加到树底部的任何小部件之后,带有所有数据的对象就可以使用了。 在此处此处详细说明如何与Provider合作。


接下来,我们需要确保当您单击按钮或滑动时,所有数据都将传输到Stream,然后在所有屏幕上,从同一Stream更新数据。


BLoC类别


为此,我们创建了一个BLoC类,在其中我们不仅描述了流,而且还描​​述了手机永久存储区中状态的接收和记录。


 import 'dart:async'; import 'package:rxdart/rxdart.dart'; import 'package:shared_preferences/shared_preferences.dart'; class SwipesBloc { Future<SharedPreferences> prefs = SharedPreferences.getInstance(); int _counter; SwipesBloc() { prefs.then((val) { if (val.get('count') != null) { _counter = val.getInt('count') ?? 1; } else { _counter = 1; } _actionController.stream.listen(_changeStream); _addValue.add(_counter); }); } final _counterStream = BehaviorSubject<int>.seeded(1); Stream get pressedCount => _counterStream.stream; void get resetCount => _actionController.sink.add(null); Sink get _addValue => _counterStream.sink; StreamController _actionController = StreamController(); StreamSink get incrementCounter => _actionController.sink; void _changeStream(data) async { if (data == null) { _counter = 1; } else { _counter = _counter + data; } _addValue.add(_counter); prefs.then((val) { val.setInt('count', _counter); }); } void dispose() { _counterStream.close(); _actionController.close(); } } 

如果我们仔细看一下这个类,我们将看到:


  1. 外部可用的任何属性都是流的输入和输出。
  2. 在设计人员的首次运行中,我们尝试从手机的永久存储中获取数据。
  3. 方便地记录在手机的永久存储中

小任务,以便更好地理解:


  • 最好从构造函数中从.then中获取一段代码,以制作单独的方法。
  • 尝试在没有提供者的情况下将此类实现为Singleton

在应用程序中接收和传输数据


现在,我们需要在单击按钮或滑动时将数据传输到Stream上,并在卡上和单独的屏幕上获取此数据。


如何执行此操作有不同的选择,我选择了经典的选择,我们将树中需要接收/传输数据到消费者的部分包装起来


 return Scaffold( body: Consumer<SwipesBloc>( builder: (context, _swipesBloc, child) { return StreamBuilder<int>( stream: _swipesBloc.pressedCount, builder: (context, snapshot) { String counterValue = snapshot.data.toString(); return Stack( children: <Widget>[ Container( 

好吧,然后获取数据
_swipesBloc.pressedCount,


资料传输
_swipesBloc.incrementCounter.add(1);


就是这样,我们在BLoC模式的规则中获得了清晰且可扩展的代码。


工作实例


测验


您可以测试小部件,可以制作Mokas,可以进行e2e。


我们将测试这些小部件并运行该应用程序,并检查计数器增加的工作方式。 有关此处此处的测试的信息。


小部件测试


如果我们有同步数据,那么我们可以使用小部件测试所有内容。 在我们的例子中,我们只能检查窗口小部件的创建方式和初始化方式。


代码在此处 ,在代码中,尝试单击后检查计数器的增加-这是错误的,因为数据通过BLoC传递。


要运行测试,请使用以下命令
flutter test


整合测试


在这个测试选项中,应用程序在模拟器上运行,我们可以按按钮,滑动并检查结果。


为此,我们创建2个文件:


test_driver / app.dart
test_driver / app_test.dart


首先,我们连接所需的内容,然后,第二步直接连接测试。 例如,我做了检查:


  • 初始状态
  • 按下按钮后计数器增加

 import 'package:flutter_driver/flutter_driver.dart'; import 'package:test/test.dart'; void main() { group( 'park-flutter app', () { final counterTextFinder = find.byValueKey('counterKey'); final buttonFinder = find.byValueKey('incrementPlusButton'); FlutterDriver driver; setUpAll(() async { driver = await FlutterDriver.connect(); }); tearDownAll(() async { if (driver != null) { driver.close(); } }); test('test init value', () async { expect(await driver.getText(counterTextFinder), ^_^quot quot^_^); }); test('test + 1 value after tapped', () async { await driver.tap(buttonFinder); // Then, verify the counter text is incremented by 1. expect(await driver.getText(counterTextFinder), ^_^quot quot^_^); }); }, ); } 

在那里编码


要运行测试,请使用以下命令
flutter drive --target=test_driver/app.dart


挑战赛


只是为了加深您的理解。 在现代应用程序(站点)中,经常使用ReactiveX的Debounce功能。


例如:


  1. 仅在字母集之间的间隔超过2秒时,在搜索栏中输入一个单词,并且提示消失
  2. 点赞后,您可以每秒点击10次-如果点击间隔超过2-3秒,则会写入数据库
  3. ...等

任务:仅在两次按+或-之间经过2秒以上时,才更改数字。 为此,仅编辑BLoC类,其余代码应保持不变。




仅此而已。 如果有什么歪斜或错误,请在此处或在github上更正,尝试实现理想效果:)


好的编码给大家!

Source: https://habr.com/ru/post/zh-CN485002/


All Articles