当前位置: 首页 > 工具软件 > bloc > 使用案例 >

【Flutter实战 BLoC模式 RxDart Provider模式】

蒲昀
2023-12-01

我认为的BLoC模式

  • 观察者模式 + 线程调度 + Dart异步语法特性 + Flutter的封装
    • Dart异步语法特性: Dart的API, Stream、Future
    • Flutter封装:StreamBuilder(封装了:Dart特性+Flutter UI)
  • 理念老生常态,所以下面的文章,重点介绍了Stream

---------分割线20210927

如何进行状态管理

  • 交互性(interactively)
  • 数据流处理
  • 状态变更
  • 组件间的交互

举例

通过底部组件的滑动,来变更饼图的状态

交互

  • 类比Android一个ProgressBar,一个饼图 PieChart
  • 滑动ProgressBar,引起饼图PieChar 数据变化

AntiPattern的设计

  • PieChart将Status设为Global 暴露出去
  • ProgressBar监听拖动,根据拖动 更改 PieChart 的State。charState.setState( () {charState.dataChange = 2 })

坏处

  • ProgressBar 和 PieChart 强耦合
  • PieChart的Status全局暴露了(Globally tracking state.)
  • 在PieChart外部调动 setState

概念

  • UI = f(state)
  • state通过Function来映射出UI
  • 从公式可以看出,没有findViewById,也没有一个View 更改了另一个View。
  • 在组件的生命周期中变更的东西:state

Stream概念回顾

  • Dart的dart:async库的两个主要API: Stream和Future。 Future代表单次计算的结果。Stream代表一串的结果。
  • 创建Stream的方式:
  • 转换(Transforming)已有的stream
  • 使用 async* 方法 创建 stream
  • 使用StreamController创建Stream

Transforming已存在的Stream

/// Splits a stream of consecutive strings into lines.
///
/// The input string is provided in smaller chunks through
/// the `source` stream.
Stream<String> lines(Stream<String> source) async* {
  // Stores any partial line from the previous chunk.
  var partial = '';
  // Wait until a new chunk is available, then process it.
  await for (var chunk in source) {
    var lines = chunk.split('\n');
    lines[0] = partial + lines[0]; // Prepend partial line.
    partial = lines.removeLast(); // Remove new partial line.
    for (var line in lines) {
      yield line; // Add lines to output stream.
    }
  }
  // Add final partial line to output stream, if any.
  if (partial.isNotEmpty) yield partial;
}

还有Stream支持的方法:

  • map() 映射
  • where() 过滤
  • expand() 重复
  • take() 限取
var counterStream =
    Stream<int>.periodic(Duration(seconds: 1), (x) => x).take(15);
///...

counterStream.forEach(print); // Print an integer every second, 15 times.


var doubleCounterStream = counterStream.map((int x) => x * 2);
doubleCounterStream.forEach(print);

///...
.where((int x) => x.isEven) // Retain only even integer events.
.expand((var x) => [x, x]) // Duplicate each event.
.take(5) // Stop after the first five events.

asynchronous generator (async*) function 异步生成器函数

Stream<int> timedCounter(Duration interval, [int? maxCount]) async* {
  int i = 0;
  while (true) {
    await Future.delayed(interval);
    yield i++;
    if (i == maxCount) break;
  }
}

异步生成器方法返回 Stream 对象

  • 当调用Stream.listen((event) { }) 时,方法体开始运行
  • 方法体:隔一段时间 产生(yield:产生、屈服)下一个数字
  • 如果没有 maxCount条件限制,则只有 listener 取消监听,才会停止
  • yield 在产生一串结果中 类比于return
  • 方法,最终error或者complete
  • 当listener cancel时,yield 相当于return,直接退出
Stream<T> streamFromFutures<T>(Iterable<Future<T>> futures) async* {
  for (var future in futures) {
    var result = await future;
    yield result;
  }
}

async*方法一般较少使用,在多数据源的处理上过于简单,所以引入了StreamController。

StreamController:

  • Sink 水槽:输入数据
  • Stream 水流:输出数据
  • StreamControllers:管理stream、sink

Stream的中间处理

  • Stream也可以在流出之前处理数据,通过StreamTransformer。输入Stream,输出Stream。
  • 一般是复用处理逻辑,可以实现 Stream.map, Stream.where or Stream.expand 一样的效果
  • 一般用于:过滤、重组、更改、删除、缓存和其他操作
  • StreamSubscription:listener设置。当至少有一个active状态的listener,Stream就会生成events,去提醒StreamSubscription。

Events:

  • some data goes out from the stream,
  • when some error has been sent to the stream,
  • when the stream is closed.

StreamSubscription功能

  • stop listening,
  • pause,
  • resume.

Stream的种类

  • 两种类型的Stream。
  • StreamController的sync属性,控制是否异步调用。
  • Single-subscription Streams
  • 只允许一个listener去监听。不允许在Stream上监听两次,即使第一个subscription (监听者)取消监听。
  StreamSubscription<List<int>> listen(
    void Function(List<int> event)? onData, {
    Function? onError,
    void Function()? onDone,
    bool? cancelOnError,
  }) {
	 if (_sub == null) {
      throw StateError('Stdin has already been terminated.');
    }
    // ignore: close_sinks
    final controller = _getCurrent();
    if (controller.hasListener) {
      throw StateError(''
          'Subscriber already listening. The existing subscriber must cancel '
          'before another may be added.');
    }
    ///...
import 'dart:async';

void main() {
  //
  // Initialize a "Single-Subscription" Stream controller
  //
  final StreamController ctrl = StreamController();
  
  //
  // Initialize a single listener which simply prints the data
  // as soon as it receives it
  //
  final StreamSubscription subscription = ctrl.stream.listen((data) => print('$data'));

  //
  // We here add the data that will flow inside the stream
  //
  ctrl.sink.add('my name');
  ctrl.sink.add(1234);
  ctrl.sink.add({'a': 'element A', 'b': 'element B'});
  ctrl.sink.add(123.45);
  
  //
  // We release the StreamController
  //
  ctrl.close();
}

Broadcast Streams

可以任意添加 listener 到 Broadcast Stream.

import 'dart:async';

void main() {
  //
  // Initialize a "Broadcast" Stream controller of integers
  //
  final StreamController<int> ctrl = StreamController<int>.broadcast();
  
  //
  // Initialize a single listener which filters out the odd numbers and
  // only prints the even numbers
  //
  final StreamSubscription subscription = ctrl.stream
					      .where((value) => (value % 2 == 0))
					      .listen((value) => print('$value'));

  //
  // We here add the data that will flow inside the stream
  //
  for(int i=1; i<11; i++){
  	ctrl.sink.add(i);
  }
  
  //
  // We release the StreamController
  //
  ctrl.close();
}

RxDart

  • 现在提到Streams,就必须提到RxDart Package
  • RxDart package 是 ReactiveX API的Dart实现版本,拓展了Dart Streams API使其符合ReactiveX标准
  • RxDart is a reactive functional programming library for Dart language, based on ReactiveX.

词汇表对比

DartRXDart
StreamStreamController
ObservableSubject

RxDart 提供了StreamController的3个变体。

PublishSubject

PublishSubject 和 广播的StreamController变体,只是返回的是Observable而不是Stream。

BehaviorSubject

BehaviorSubject和PublishSubject 不同点: subscribe(订阅)时,返回订阅前 最新的event。

ReplaySubject

ReplaySubject和PublishSubject 不同点: subscribe(订阅)时,返回订阅前 所有的event。

注意

在不需要时,一定要释放资源

  • StreamSubscription - when you no longer need to listen to a stream, cancel the subscription;
  • StreamController - when you no longer need a StreamController, close it;
  • the same applies to RxDart Subjects, when you no longer need a BehaviourSubject, a PublishSubject…, close it.

What is Reactive Programming?

  • Reactive programming is programming with asynchronous data streams.
    • 使用 异步数据流 编程。
  • In other words, everything from an event (e.g. tap), changes on a variable, messages, … to build requests, everything that may change or happen will be conveyed, triggered by a data stream.
    • 也就是说,任何…都被 数据流 触发、传递。

使用reactive programming,程序会:

  • becomes asynchronous,变得异步
  • is architectured around the notion of Streams and listeners,架构围绕着Streams和listeners概念
  • when something happens somewhere (an event, a change of a variable …) a notification is sent to a Stream, 变更(事件、变量的改变)会给stream发送提醒。
  • if “somebody” listens to that Stream, it will be notified and will take appropriate action(s), whatever its location in the application. 无论监听者在程序的哪里,都会被通知。
  • There is no longer any tight coupling between components. that Widget does only care about its own business, that’s all !!
    组件之间不再耦合,组件只关心自己的业务逻辑。
  • In short, when a Widget sends something to a Stream, that Widget does no longer need to know: 组件发送事件给Stream是,不再关心
    • what is going to happen next, 接下来发生什么
      who might use this information (no one, one or several Widgets…) 信息被谁使用
    • where this information might be used (nowhere, same screen, another one, several ones…), 信息在哪里被使用
    • when this information might be used (almost directly, after several seconds, never…).信息何时被使用

好处:

  • the opportunity to build parts of the application only responsible for specific activities, 单一职责原则
  • to easily mock some components’ behavior to allow more complete tests coverage, 方便mock组件的行为,增加测试覆盖
  • to easily reuse components (somewhere else in the application or in another application), 方便组件复用

BLoC模式

  • 理念是万物皆流,一部分组件订阅事件,另一部分组件则响应事件。
  • BLoC居中管理这些会话。
  • Dart也把流(Stream)内置到了语言本身里,不用依赖额外的库

Business Logic Components 的缩写

  • 使用Dart stream控制组件中的数据流,不用引入依赖
  • The BLoC Pattern has been designed by Paolo Soares and Cong Hui, from Google and first presented during the DartConf 2018 (January 23-24, 2018). See the video on YouTube. 为了从

架构

名词解释

  • Provider组件: 绑定 bloc的 UI组件,并提供子Widget访问 bloc的方法
  • StreamBuilder组件:监听stream的输出,调用builder刷新UI

在Flutter中:

  • the pipe is called a Stream
  • to control the Stream, we usually(*) use a StreamController
  • to insert something into the Stream, the StreamController exposes the “entrance", called a StreamSink, accessible via the sink property.
  • the way out of the Stream, is exposed by the StreamController via the stream property

不使用flutter_bloc库

  ///网络请求处理
  final _dataSourceController = StreamController<ModelConfig>();
  StreamSink<ModelConfig> get configSink => _dataSourceController.sink;
	Stream<ModelConfig> get configStream => _dataSourceController.stream;
  ///点击事件分发
  final _eventController = StreamController<UserEvent>();	
	StreamSink<UserEvent> get dispatch => _eventController.sink;
  /// UI内容展示
  final _displayController = StreamController<String>();
  Stream<String> get displayContent => _displayController.stream;
  
  
  enum UserEvent {
		add, delete,modify
  }
  
  initBloc() {
  	eventController.listen((event) {	
    	Switch (event) {
      	case UserEvent.add:
       		handleUserAdd();
        	break;
		///...
    });
  }
  
  void handleUserAdd() {
  	_displayController.sink.add('User click add button');
  }
  ConfigRepository.instance.get((config) {
		configSink.add(config);
  });
import 'package:flutter/cupertino.dart';
import 'package:flutter_app/bloc/bloc.dart';

class BlocProvider<T extends Bloc> extends StatefulWidget {
  final Widget child;
  final T bloc;

  const BlocProvider({Key? key, required this.bloc, required this.child})
      : super(key: key);

  static T? of<T extends Bloc>(BuildContext context) {
    // final type = _providerType<BlocProvider<T>>();
    final BlocProvider<T>? provider = context.findAncestorWidgetOfExactType<BlocProvider<T>>();
    return provider?.bloc;
  }

  static Type _providerType<T>() => T;

  @override
  State<StatefulWidget> createState() => _BlocProviderState();

}

class _BlocProviderState extends State<BlocProvider> {


  @override
  Widget build(BuildContext context) => widget.child;

  @override
  void dispose() {
    widget.bloc.dispose();
    super.dispose();
  }
}

创建BLoC,并通过BlocProvider 提供子Widget获取

void main() {
  runApp(BlocProvider(
    bloc: MainBloc(),
    child: MaterialApp(
      title: 'MaoShan',
      home: RoutingPage()
    ),
  ));
}

监听Stream

  StreamSubscription<T> listen(void onData(T event),
      {Function onError, void onDone(), bool cancelOnError}) 

  @override
  Widget build(BuildContext context) {
    final MainBloc? bloc = BlocProvider.of<MainBloc>(context);

    resetController(widget.config);
    return Scaffold(
        appBar: AppBar(
          title: StreamBuilder<String>(
              stream: bloc?.displayContent,
              builder: (BuildContext context, AsyncSnapshot<String?> snapshot) {
                return Text(snapshot?.data ?? '');
              }),
        ),
        body: Container(
            child: ElevatedButton(
                child: Text("Add something"),
                onPressed: () => bloc?.dispatch.add)));
  }

使用flutter_bloc库

BLoC内部架构:

  • 代码架构上更符合: UI = f(state),适合大型项目使用

安装:

  • 添加依赖包,在pubspec.yanml flutter_bloc: ^7.0.1
  • IDE的模板插件,AndroidStudio搜BLoC
  • 新建bloc,输入名称 prefix。自动生成3个文件
    • prefix_bloc.dart
    • prefix_state.dart
    • prefix_event.dart

流程:

bloc内容

mapEventToState将Event转换为State

class MainBloc extends Bloc<MainEvent, MainState> {
  MainBloc() : super(MainInitial());

  @override
  Stream<MainState> mapEventToState(
    MainEvent event,
  ) async* {
    // TODO: implement mapEventToState
  }
}
///...
class MainBloc extends Bloc<MainEvent, MainState> {
  MainBloc() : super(MainState(selectedIndex: 0, isExtended: false));

  @override
  Stream<MainState> mapEventToState(MainEvent event) async* {
    ///main_view中添加的事件,会在此处回调,此处处理完数据,将数据yield,BlocBuilder就会刷新组件
    if (event is SwitchTabEvent) {
      ///获取到event事件传递过来的值,咱们拿到这值塞进MainState中
      ///直接在state上改变内部的值,然后yield,只能触发一次BlocBuilder,它内部会比较上次MainState对象,如果相同,就不build
      yield MainState()
        ..selectedIndex = event.selectedIndex
        ..isExtended = state.isExtended;
    } else if (event is IsExtendEvent) {
      yield MainState()
        ..selectedIndex = state.selectedIndex
        ..isExtended = !state.isExtended;
    }
  }
}

main_event:这里是执行的各类事件,有点类似fish_redux的action层

@immutable
abstract class MainEvent {}

///...
@immutable
abstract class MainEvent extends Equatable{
  const MainEvent();
}
///切换NavigationRail的tab
class SwitchTabEvent extends MainEvent{
  final int selectedIndex;

  const SwitchTabEvent({@required this.selectedIndex});

  @override
  List<Object> get props => [selectedIndex];
}
///展开NavigationRail,这个逻辑比较简单,就不用传参数了
class IsExtendEvent extends MainEvent{
  const IsExtendEvent();

  @override
  List<Object> get props => [];
}

main_state:状态数据放在这里保存,中转

@immutable
abstract class MainState {}
///...
class MainState{
   int selectedIndex;
   bool isExtended;
  
   MainState({this.selectedIndex, this.isExtended});
}

Ui页面编写:发送event、更改UI

class MainPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return _buildBg(children: [
      //侧边栏
      _buildLeftNavigation(),

      //右边主体内容
      Expanded(child: Center(
        child: BlocBuilder<MainBloc, MainState>(builder: (context, state) {
          return Text(
            "选择Index:" + state.selectedIndex.toString(),
            style: TextStyle(fontSize: 30.0),
          );
        }),
      ))
    ]);
  }

  Widget _buildBg({List<Widget> children}) {
    ///创建BlocProvider的,表明该Page,我们是用MainBloc,MainBloc是属于该页面的Bloc了
    return BlocProvider(
      create: (BuildContext context) => MainBloc(),
      child: Scaffold(
        appBar: AppBar(title: Text('Bloc')),
        body: Row(children: children),
      ),
    );
  }

  //增加NavigationRail组件为侧边栏
  Widget _buildLeftNavigation() {
    return BlocBuilder<MainBloc, MainState>(builder: (context, state) {
      return NavigationRail(
        backgroundColor: Colors.white,
        elevation: 3,
        extended: state.isExtended,
        labelType: state.isExtended
            ? NavigationRailLabelType.none
            : NavigationRailLabelType.selected,
        //侧边栏中的item
        destinations: [
          NavigationRailDestination(
            icon: Icon(Icons.add_to_queue),
            selectedIcon: Icon(Icons.add_to_photos),
            label: Text("测试一"),
          ),
          NavigationRailDestination(
            icon: Icon(Icons.add_circle_outline),
            selectedIcon: Icon(Icons.add_circle),
            label: Text("测试二"),
          ),
          NavigationRailDestination(
            icon: Icon(Icons.bubble_chart),
            selectedIcon: Icon(Icons.broken_image),
            label: Text("测试三"),
          ),
        ],
        //顶部widget
        leading: _buildNavigationTop(),
        //底部widget
        trailing: _buildNavigationBottom(),
        selectedIndex: state.selectedIndex,
        onDestinationSelected: (int index) {
          ///添加切换tab事件
          BlocProvider.of<MainBloc>(context)
              .add(SwitchTabEvent(selectedIndex: index));
        },
      );
    });
  }

  Widget _buildNavigationTop() {
    return Center(
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Container(
          width: 80,
          height: 80,
          decoration: BoxDecoration(
            shape: BoxShape.circle,
            image: DecorationImage(
              image: NetworkImage(
                "https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3383029432,2292503864&fm=26&gp=0.jpg",
              ),
              fit: BoxFit.fill,
            ),
          ),
        ),
      ),
    );
  }

  Widget _buildNavigationBottom() {
    return Container(
      child: BlocBuilder<MainBloc, MainState>(
        builder: (context, state) {
          return FloatingActionButton(
            onPressed: () {
              ///添加NavigationRail展开,收缩事件
              BlocProvider.of<MainBloc>(context).add(IsExtendEvent());
            },
            child: Icon(state.isExtended ? Icons.send : Icons.navigation),
          );
        },
      ),
    );
  }
}

API:

BlocWidgets 类比于 StreamBuilder

BlocBuilder<BlocA, BlocAState>(
  bloc: blocA, // provide the local bloc instance
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)


BlocBuilder<BlocA, BlocAState>(
  buildWhen: (previousState, state) {
    /// return 是否重新运行
  },
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)

BlocProvider

BlocProvider.value(
  value: BlocProvider.of<BlocA>(context),
  child: ScreenA(),
);


/// 使用
{
// with extensions
context.read<BlocA>();

// without extensions
BlocProvider.of<BlocA>(context);
}

MultiBlocProvider

/// Rrovider 嵌套问题

MultiBlocProvider(
  providers: [
    BlocProvider<BlocA>(
      create: (BuildContext context) => BlocA(),
    ),
    BlocProvider<BlocB>(
      create: (BuildContext context) => BlocB(),
    ),
    BlocProvider<BlocC>(
      create: (BuildContext context) => BlocC(),
    ),
  ],
  child: ChildA(),
)

BlocListener

listener方法没用return值,用于 展示SnackBar,Dialog

BlocListener<BlocA, BlocAState>(
  listener: (context, state) {
    // do stuff here based on BlocA's state
  },
  child: Container(),
)

BlocConsumer

融合了BlocListener 和BlocBuilder

BlocConsumer<BlocA, BlocAState>(
  listenWhen: (previous, current) {
    // return true/false to determine whether or not
    // to invoke listener with state
  },
  listener: (context, state) {
    // do stuff here based on BlocA's state
  },
  buildWhen: (previous, current) {
    // return true/false to determine whether or not
    // to rebuild the widget with state
  },
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)

Bloc

enum TestEvent { increment }

class TestBloc extends Bloc<TestEvent, int> {
  TestBloc(int i) : super(i);

  @override
  Stream<int> mapEventToState(TestEvent event) async* {
    switch (event) {
      case TestEvent.increment:
        yield state + 1;
        break;
    }
  }
}

Provider 模式

  • 社区提供的解决方案 package:provider (provide的2.0版本)
  • 简化BLoC,降低复杂度。不用学习Stream、RX。

主要API

  • ChangeNotifier 被观察者

    • addListener(VoidCallback listener)
    • removeListener(VoidCallback listener)
    • notifyListeners()
  • ChangeNotifierProvider 观察者、Widget

Key key

@required T value

Widget child

/// ChangeNotifierProvider(/// create: (_) => new MyChangeNotifier(),/// child: .../// )
  • Consumer 特殊的观察者(消费者)

性能好点(具体看注解)

  1. 封装状态到MySchedule
  2. 使用ChangeNotifierProvider在MyApp(两个Widget共同Parent)中wrap原来布局
  3. 子Widget中读取状态 final schedule = Provider.of(context);
  4. MyChart是StateLessWidget,build中用 Consumer (builder: (context, schedule, _) => PieChart(function(schedule.stateManagementTime)…))

总结

  • BLoC模式中要注意控制 数据更新的粒度
  • BLoC 和 Provider 效果类似,类似观察者模式+线程调度
  • 但BLoC基于流,更贴近Dart语言的特性,有利于学习Dart异步,也可以熟悉 另一套理念。

参考资料:

之后再填吧,我是写在了内部网站,这是copy的

  • 挺好的BLoC快速接触文档 reactive-programming-streams-bloc教程
    reactive-programming-streams-bloc 代码示例 StateManagement 教程
    StateManagement 代码示例 BLoC官方文档
    框架大杂烩(bloc、redux、mvc、inherited_widget、mvi) BLoC教程 Dart Stream教程
 类似资料: