Flutter中的BLoC,你所需要知道的一切

Flutter中的BLoC(Business Logic Component)是一种用于构建可重用的业务逻辑组件的架构模式。它基于单一责任原则,将业务逻辑从UI层分离出来,并通过流(Stream)将它们连接起来。下面是对BLoC的更详细的介绍:

概念:

BLoC是一种基于单一责任原则的架构模式,它将应用程序分为三个主要部分:视图(View)、业务逻辑(Business Logic)和数据(Data)。BLoC的主要思想是将业务逻辑与UI分离,并通过流将它们连接起来。


0_EG9GW70kfdesSPRZ.png

功能:

BLoC的主要功能是将业务逻辑与UI分离,从而使得应用程序更易于维护和扩展。它还允许开发人员将业务逻辑组件化,从而可以在不同的应用程序中重用。

原理:

BLoC基于流(Stream)的概念,使用RxDart库中的StreamController和Stream来实现。BLoC将UI层(如widget)中的用户操作通过事件(Event)发送给业务逻辑层,并根据这些事件处理数据并生成新的状态(State),再将新状态传递回UI层以更新视图。

优点:

1、代码重用性:BLoC可以将业务逻辑组件化,从而可以在不同的应用程序中重用。
2、分离关注点:BLoC使得业务逻辑与UI分离,使得应用程序更易于维护和扩展。
3、可测试性:BLoC的业务逻辑可以通过单元测试进行测试,从而提高代码的质量和可靠性。

缺点:

1、学习成本:学习BLoC需要一定的学习成本,因为它需要掌握一些新的概念和技术。
2、增加代码量:使用BLoC需要编写更多的代码,因为它需要将业务逻辑从UI层中分离出来。

使用方法:

1、安装RxDart库:BLoC使用RxDart库中的StreamController和Stream来实现。因此,需要安装RxDart库。
2、创建BLoC类:创建一个BLoC类来处理业务逻辑。BLoC类通常包含一个StreamController和一个Stream。
3、在UI层中使用BLoC:在UI层中使用BLoC,将用户操作转换为事件,并根据BLoC的状态来更新UI。

使用范例:

假设我们要开发一个计数器应用程序,可以使用BLoC来处理计数器的逻辑。下面是一个使用BLoC实现的简单计数器应用程序的代码:

import 'package:flutter/material.dart';
import 'package:rxdart/rxdart;

class CounterBloc {
  int _counter = 0;
  final _counterController = BehaviorSubject<int>();

  Stream<int> get counterStream => _counterController.stream;

  void incrementCounter() {
    _counter++;
    _counterController.add(_counter);
  }

  void dispose() {
    _counterController.close();
  }
}

class CounterApp extends StatefulWidget {
  @override
  _CounterAppState createState() => _CounterAppState();
}

class _CounterAppState extends State<CounterApp> {
  final _bloc = CounterBloc();

  @override
  void dispose() {
    _bloc.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Counter')),
      body: Center(
        child: StreamBuilder<int>(
          stream: _bloc.counterStream,
          initialData: 0,
          builder: (context, snapshot) {
            return Text('Count: ${snapshot.data}');
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          _bloc.incrementCounter();
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

在这个示例中,我们首先创建了一个CounterBloc类来处理计数器的逻辑。CounterBloc类包含一个计数器变量_counter和一个StreamController _counterController,用于向UI层发送计数器变化的事件。

我们使用getter方法counterStream将CounterBloc类中的_streamController暴露为流Stream<int>。incrementCounter方法用于增加计数器变量_counter的值,并通过_counterController向UI层发送新的计数器值。最后,我们通过dispose方法来关闭CounterBloc类中的流StreamController。

在UI层中,我们创建一个CounterApp小部件,它包含一个StreamBuilder小部件,用于在UI层中显示计数器的值。我们在CounterApp的状态类_CounterAppState中创建CounterBloc的实例_bloc,并在小部件的dispose方法中调用bloc.dispose()来关闭CounterBloc中的StreamController。

我们将_bloc.counterStream流传递给StreamBuilder,这样我们就可以监听计数器变化的事件。StreamBuilder会自动重建并更新UI层,以反映新的计数器值。在floatingActionButton中,我们使用_bloc.incrementCounter()方法将新的计数器值发送到CounterBloc类中。

使用过程注意点:

1、BLoC的使用需要掌握RxDart库中的StreamController和Stream概念,并理解BLoC的核心思想。
2、在使用BLoC时,应该尽量将业务逻辑从UI层分离出来,以使代码更易于维护和扩展。
3、在使用BLoC时,应该注意处理好流的生命周期,以防止内存泄漏问题的出现。要及时调用流的dispose方法来释放资源。
4、使用BLoC可以使应用程序更易于测试,因此建议使用单元测试来确保代码的质量和可靠性。

优点:

1、BLoC可以将业务逻辑和UI层分离,使代码更易于维护和扩展。
2、BLoC使用流来处理数据,可以实现响应式编程,让UI层更加流畅和灵活。
3、BLoC可以轻松地进行单元测试,因为业务逻辑和UI层分离,可以独立地测试业务逻辑的正确性。
4、BLoC可以在多个屏幕之间共享数据和状态,使得应用程序的代码更加清晰和模块化。

缺点:

1、使用BLoC需要掌握一定的Rx编程知识,学习成本较高。
2、BLoC中的代码比较复杂,容易出现嵌套回调地狱的情况,需要合理的代码组织和封装。
3、BLoC的性能可能会受到影响,特别是在处理大量数据时。

使用方法:

1、引入rxdart库:在pubspec.yaml文件中添加rxdart依赖。

dependencies: 
  rxdart: ^0.27.2

2、创建BLoC类:BLoC类应该包含数据处理逻辑和状态管理的方法,以及StreamController来向UI层发送数据更新事件。BLoC类应该根据业务逻辑来定义,可以参考上面的计数器示例。

3、在UI层使用StreamBuilder:使用StreamBuilder来监听BLoC类中的流,以反映数据的变化。StreamBuilder会自动重建并更新UI层。

4、在UI层中使用BLoC:在UI层中实例化BLoC类,并在需要的地方调用BLoC中的方法来处理业务逻辑和状态管理。

使用范例:

1、Flutter Gallery中的BLoC示例:https://github.com/flutter/gallery/tree/master/lib/bloc
2、Flutter官方文档中的BLoC示例:https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple#bloc

使用过程注意点:

1、BLoC的使用需要掌握RxDart库中的StreamController和Stream概念,并理解BLoC的核心思想。
2、在使用BLoC时,应该尽量将业务逻辑从UI层分离出来,以使代码更易于维护和扩展。
3、在使用BLoC时,应该注意处理好流的生命周期,以防止内存泄漏问题的出现。要及时调用流的dispose方法来释放资源。
4、使用BLoC可以使应用程序更易于测试,因此建议使用单元测试来确保代码的质量和可靠性。
5、在使用BLoC时,需要注意在BLoC类中维护数据的一致性和正确性,特别是在多个UI层中共享同一个BLoC对象时,要确保数据的正确性。
6、在BLoC类中处理异步操作时,应该使用异步函数和异步等待来处理异步任务,以避免造成UI线程的阻塞和卡顿现象。
7、在使用BLoC时,可以使用第三方库如flutter_bloc来简化BLoC的使用和管理,该库提供了许多便捷的工具和API来处理常见的业务逻辑和状态管理。
8、在使用BLoC时,应该遵循单一职责原则和依赖倒置原则,尽可能地将业务逻辑和状态管理从UI层分离出来,并使用依赖注入来实现解耦。
9、在使用BLoC时,可以通过使用流的操作符来处理数据,如map、where、fold等操作符,以实现更加复杂的数据转换和处理逻辑。
10、在使用BLoC时,需要注意错误处理和异常捕获,特别是在处理异步操作时,需要使用try-catch语句来捕获异步任务中的异常,并向上层传递错误信息。

另外:

1、BLoC是一种可复用的模式,可以在整个应用程序中使用。它是一种非常灵活的模式,可以根据不同的需求进行定制和扩展。
2、在使用BLoC时,可以通过组合和嵌套的方式来构建复杂的业务逻辑和状态管理。例如,可以将多个BLoC对象组合在一起,以处理更加复杂的数据流和业务逻辑。
3、BLoC可以与其他Flutter框架和库一起使用,如Provider、GetIt、MobX等。这些框架和库可以进一步简化BLoC的使用和管理,以提高代码的可读性和可维护性。
4、BLoC可以通过使用Code Generator来生成BLoC类的模板代码,以提高开发效率和代码质量。例如,可以使用flutter_bloc库提供的bloc_template命令来生成BLoC类的模板代码。
5、在使用BLoC时,可以通过使用Flutter DevTools来调试和分析数据流,以实现更加高效的调试和优化。

flutter_bloc插件 :https://pub.dev/packages/bloc

flutter_bloc是一个Flutter状态管理库,它的主要功能是帮助开发者更好地组织和管理Flutter应用程序中的状态,并且将UI和业务逻辑分离,使得代码更加清晰、易于维护。

Flutter自带的Bloc库是Google官方提供的,而flutter_bloc是由第三方开发者创建和维护的,它们有一些联系和区别。

联系:

1、基本概念类似:

Flutter自带的Bloc库和flutter_bloc库的基本概念是类似的,包括Bloc、Event和State等。

2、都可以实现状态管理

Flutter自带的Bloc库和flutter_bloc库都可以实现状态管理,通过将状态存储在Bloc中,并使用Stream和yield关键字将状态输出到UI层。

3、都适用于Flutter应用程序

Flutter自带的Bloc库和flutter_bloc库都是为Flutter应用程序而设计的,可以在Flutter应用程序中使用。

区别:

1、使用方式不同

Flutter自带的Bloc库使用的是原生的Stream API和语言特性,而flutter_bloc库使用了更高级的Dart语言特性和第三方库,例如equatable和hydrated_bloc。

2、功能和特性不同

Flutter自带的Bloc库提供了一些基本的功能和特性,例如处理异步事件和状态管理等。而flutter_bloc库提供了更多的功能和特性,例如自动化测试、依赖注入、高级状态管理等。

功能

image.png

1、提供了一个Bloc类和一个Cubit类,用于实现业务逻辑和状态管理。Bloc类是一个抽象类,它提供了基本的状态管理功能和事件处理方法,而Cubit类则是Bloc类的一个子类,它提供了更加简单的API和更少的模板代码。
2、提供了一个BlocBuilder小部件和一个BlocListener小部件,用于在UI层中监听BLoC的状态变化并更新UI。BlocBuilder小部件可以根据BLoC的状态自动构建UI,并在状态变化时自动更新UI,而BlocListener小部件则可以监听BLoC的状态变化并执行一些副作用操作。
3、提供了一个MultiBlocProvider小部件和一个BlocProvider小部件,用于在应用程序中管理多个BLoC对象和依赖注入。MultiBlocProvider小部件可以同时管理多个BLoC对象,而BlocProvider小部件则可以为子树提供一个BLoC对象。

插件使用举例

首先,需要在pubspec.yaml文件中添加flutter_bloc依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_bloc: ^7.3.1

然后,创建一个CounterBloc类来实现计数器的业务逻辑和状态管理:

import 'package:bloc/bloc.dart';

class CounterBloc extends Bloc<int, int> {
  CounterBloc() : super(0);

  @override
  Stream<int> mapEventToState(int event) async* {
    yield state + event;
  }
}

在这个例子中,CounterBloc类继承自Bloc类,并定义了一个泛型类型参数int,表示BLoC的状态类型和事件类型都是整数。CounterBloc类的构造函数中,使用super调用父类的构造函数,将初始状态设置为0。

然后,重写mapEventToState方法,用于处理事件和更新状态。在这个例子中,事件是一个整数,表示计数器需要增加的值,而状态是一个整数,表示计数器的当前值。在mapEventToState方法中,将事件与当前状态相加,并通过yield关键字将新的状态作为流的输出。

接下来,使用BlocProvider小部件将CounterBloc对象提供给UI层:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (BuildContext context) => CounterBloc(),
      child: MaterialApp(
        title: 'Flutter Bloc Example',
        home: MyHomePage(),
      ),
    );
  }
}

在这个例子中,使用BlocProvider小部件将CounterBloc对象提供给UI层。在BlocProvider的create回调函数中,创建一个CounterBloc对象,并将其提供给BlocProvider。然后,在MyHomePage小部件中,使用BlocBuilder小部件和BlocProvider.of方法来监听CounterBloc的状态变化并更新UI:

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Bloc Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            BlocBuilder<CounterBloc, int>(
              builder: (context, state) {
                return Text(
                  '$state',
                  style: TextStyle(fontSize: 24.0),
                );
              },
            ),
            SizedBox(height: 20.0),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                FloatingActionButton(
                  onPressed: () {
                    BlocProvider.of<CounterBloc>(context).add(1);
                  },
                  child: Icon(Icons.add),
                ),
                SizedBox(width: 20.0),
                FloatingActionButton(
                  onPressed: () {
                    BlocProvider.of<CounterBloc>(context).add(-1);
                  },
                  child: Icon(Icons.remove),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

在上面的代码中,BlocBuilder小部件监听CounterBloc的状态变化,并在状态变化时自动构建UI,其中context.read<CounterBloc>()可以用来获取CounterBloc实例,并通过add方法向其发送事件。

这样,就实现了一个简单的计数器应用程序,通过使用flutter_bloc插件的BlocProvider和BlocBuilder小部件来实现BLoC模式的业务逻辑和状态管理。

高级功能

除了上面提到的基本使用方法外,flutter_bloc插件还提供了一些高级功能,下面简单介绍一下:

多Bloc的使用

有时候,在应用程序中需要使用多个Bloc来处理不同的业务逻辑,此时可以使用MultiBlocProvider小部件来提供多个Bloc实例,例如:

MultiBlocProvider(
  providers: [
    BlocProvider<CounterBloc>(
      create: (context) => CounterBloc(),
    ),
    BlocProvider<TimerBloc>(
      create: (context) => TimerBloc(),
    ),
  ],
  child: MyApp(),
)

高级状态管理

在一些复杂的应用程序中,状态可能会非常复杂,此时可以使用flutter_bloc插件提供的State类和Equatable库来实现高级状态管理。

例如,定义一个复杂的状态类:

class MyState extends Equatable {
  final int count;
  final String text;

  MyState({this.count, this.text});

  MyState copyWith({int count, String text}) {
    return MyState(
      count: count ?? this.count,
      text: text ?? this.text,
    );
  }

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

上面的代码中,MyState类继承自Equatable类,通过重写props属性来定义对象相等的比较方法。

然后,在Bloc中使用MyState类作为状态:

class MyBloc extends Bloc<MyEvent, MyState> {
  MyBloc() : super(MyState(count: 0, text: ''));

  @override
  Stream<MyState> mapEventToState(MyEvent event) async* {
    if (event is IncrementEvent) {
      yield state.copyWith(count: state.count + 1);
    } else if (event is DecrementEvent) {
      yield state.copyWith(count: state.count - 1);
    } else if (event is SetTextEvent) {
      yield state.copyWith(text: event.text);
    }
  }
}

在上面的代码中,MyBloc使用MyState作为状态,并通过copyWith方法来生成新的状态对象。

最后,在UI层中使用BlocBuilder小部件来监听MyBloc的状态变化,并更新UI:

BlocBuilder<MyBloc, MyState>(
  builder: (context, state) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text(
          'Count: ${state.count}',
          style: TextStyle(fontSize: 24),
        ),
        SizedBox(height: 24),
        Text(
          'Text: ${state.text}',
          style: TextStyle(fontSize: 24),
        ),
      ],
    );
  },
)

在上面的代码中,使用BlocBuilder小部件来监听MyBloc的状态变化,并在状态变化时自动构建UI。

通过使用Equatable库和State类,可以更好地管理复杂的状态,并提高应用程序的可维护性。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容