imtoken钱包最新版下载i|bloc

作者: imtoken钱包最新版下载i
2024-03-07 17:25:57

BLOC中文(简体)翻译:剑桥词典

BLOC中文(简体)翻译:剑桥词典

词典

翻译

语法

同义词词典

+Plus

剑桥词典+Plus

Shop

剑桥词典+Plus

我的主页

+Plus 帮助

退出

剑桥词典+Plus

我的主页

+Plus 帮助

退出

登录

/

注册

中文 (简体)

查找

查找

英语-中文(简体)

bloc 在英语-中文(简体)词典中的翻译

blocnoun [ C ] uk

Your browser doesn't support HTML5 audio

/blɒk/ us

Your browser doesn't support HTML5 audio

/blɑːk/

Add to word list

Add to word list

a group of countries or people that have similar political interests

集团,阵营(指具有类似政治利益的国家或人的群体)

The European Union is a powerful trading/trade bloc.

欧盟是一个强大的贸易集团。

the former Eastern/Communist bloc countries

前东欧集团/共产主义阵营国家

同义词

axis

(bloc在剑桥英语-中文(简体)词典的翻译 © Cambridge University Press)

B1

bloc的翻译

中文(繁体)

集團,陣營(指具有類似政治利益的國家或人的群體)…

查看更多内容

西班牙语

bloque…

查看更多内容

葡萄牙语

bloco…

查看更多内容

更多语言

土耳其语

法语

in Dutch

捷克语

丹麦语

印尼语

泰语

越南语

波兰语

in Swedish

马来语

德语

挪威语

in Ukrainian

俄语

bir grup ülkenin aralarında oluşturdukları blok, cephe, blok…

查看更多内容

bloc…

查看更多内容

blok…

查看更多内容

blok…

查看更多内容

blok…

查看更多内容

blok…

查看更多内容

กลุ่มประเทศ…

查看更多内容

khối (chính trị)…

查看更多内容

blok…

查看更多内容

block, sammanslutning…

查看更多内容

blok…

查看更多内容

der (politische) Block…

查看更多内容

blokk…

查看更多内容

блок, об'єднання…

查看更多内容

блок…

查看更多内容

需要一个翻译器吗?

获得快速、免费的翻译!

翻译器工具

bloc的发音是什么?

在英语词典中查看 bloc 的释义

浏览

bloater

bloating

bloatware

blob

bloc

block

block and tackle

block capitals

block graph

bloc更多的中文(简体)翻译

全部

en bloc

black bloc

Black Bloc, at black bloc

查看全部意思»

“每日一词”

veggie burger

UK

Your browser doesn't support HTML5 audio

/ˈvedʒ.i ˌbɜː.ɡər/

US

Your browser doesn't support HTML5 audio

/ˈvedʒ.i ˌbɝː.ɡɚ/

a type of food similar to a hamburger but made without meat, by pressing together small pieces of vegetables, seeds, etc. into a flat, round shape

关于这个

博客

Forget doing it or forget to do it? Avoiding common mistakes with verb patterns (2)

March 06, 2024

查看更多

新词

stochastic parrot

March 04, 2024

查看更多

已添加至 list

回到页面顶端

内容

英语-中文(简体)翻译

©剑桥大学出版社与评估2024

学习

学习

学习

新词

帮助

纸质书出版

Word of the Year 2021

Word of the Year 2022

Word of the Year 2023

开发

开发

开发

词典API

双击查看

搜索Widgets

执照数据

关于

关于

关于

无障碍阅读

剑桥英语教学

剑桥大学出版社与评估

授权管理

Cookies与隐私保护

语料库

使用条款

京ICP备14002226号-2

©剑桥大学出版社与评估2024

剑桥词典+Plus

我的主页

+Plus 帮助

退出

词典

定义

清晰解释自然的书面和口头英语

英语

学习词典

基础英式英语

基础美式英语

翻译

点击箭头改变翻译方向。

双语词典

英语-中文(简体)

Chinese (Simplified)–English

英语-中文(繁体)

Chinese (Traditional)–English

英语-荷兰语

荷兰语-英语

英语-法语

法语-英语

英语-德语

德语-英语

英语-印尼语

印尼语-英语

英语-意大利语

意大利语-英语

英语-日语

日语-英语

英语-挪威语

挪威语-英语

英语-波兰语

波兰语-英语

英语-葡萄牙语

葡萄牙语-英语

英语-西班牙语

西班牙语-英语

English–Swedish

Swedish–English

半双语词典

英语-阿拉伯语

英语-孟加拉语

英语-加泰罗尼亚语

英语-捷克语

英语-丹麦语

English–Gujarati

英语-印地语

英语-韩语

英语-马来语

英语-马拉地语

英语-俄语

English–Tamil

English–Telugu

英语-泰语

英语-土耳其语

英语-乌克兰语

English–Urdu

英语-越南语

翻译

语法

同义词词典

Pronunciation

剑桥词典+Plus

Shop

剑桥词典+Plus

我的主页

+Plus 帮助

退出

登录 /

注册

中文 (简体)  

Change

English (UK)

English (US)

Español

Русский

Português

Deutsch

Français

Italiano

中文 (简体)

正體中文 (繁體)

Polski

한국어

Türkçe

日本語

Tiếng Việt

हिंदी

தமிழ்

తెలుగు

关注我们

选择一本词典

最近的词和建议

定义

清晰解释自然的书面和口头英语

英语

学习词典

基础英式英语

基础美式英语

语法与同义词词典

对自然书面和口头英语用法的解释

英语语法

同义词词典

Pronunciation

British and American pronunciations with audio

English Pronunciation

翻译

点击箭头改变翻译方向。

双语词典

英语-中文(简体)

Chinese (Simplified)–English

英语-中文(繁体)

Chinese (Traditional)–English

英语-荷兰语

荷兰语-英语

英语-法语

法语-英语

英语-德语

德语-英语

英语-印尼语

印尼语-英语

英语-意大利语

意大利语-英语

英语-日语

日语-英语

英语-挪威语

挪威语-英语

英语-波兰语

波兰语-英语

英语-葡萄牙语

葡萄牙语-英语

英语-西班牙语

西班牙语-英语

English–Swedish

Swedish–English

半双语词典

英语-阿拉伯语

英语-孟加拉语

英语-加泰罗尼亚语

英语-捷克语

英语-丹麦语

English–Gujarati

英语-印地语

英语-韩语

英语-马来语

英语-马拉地语

英语-俄语

English–Tamil

English–Telugu

英语-泰语

英语-土耳其语

英语-乌克兰语

English–Urdu

英语-越南语

词典+Plus

词汇表

选择语言

中文 (简体)  

English (UK)

English (US)

Español

Русский

Português

Deutsch

Français

Italiano

正體中文 (繁體)

Polski

한국어

Türkçe

日本語

Tiếng Việt

हिंदी

தமிழ்

తెలుగు

内容

英语-中文(简体) 

 Noun

Translations

语法

所有翻译

我的词汇表

把bloc添加到下面的一个词汇表中,或者创建一个新词汇表。

更多词汇表

前往词汇表

对该例句有想法吗?

例句中的单词与输入词条不匹配。

该例句含有令人反感的内容。

取消

提交

例句中的单词与输入词条不匹配。

该例句含有令人反感的内容。

取消

提交

Flutter:BLoC 模式入门教程 - 知乎

Flutter:BLoC 模式入门教程 - 知乎切换模式写文章登录/注册Flutter:BLoC 模式入门教程Pandorox原文:Getting Started with the BLoC Pattern作者:Brian Kayfitz了解如何使用流行的 BLoC 模式来构建 Flutter 应用程序,并使用 Dart streams 管理通过 Widgets 的数据流。设计应用程序的结构通常是应用程序开发中争论最激烈的话题之一。每个人似乎都有他们最喜欢的、带有花哨首字母缩略词的架构模式。iOS 和 Android 开发人员精通 Model-View-Controller(MVC),并将其作为构建应用程序的默认选择。Model 和 View 是分开的,Controller 负责在它们之间发送信号。然而, Flutter 带来了一种新的响应式风格,其与 MVC 并不完全兼容。这个经典模式的一个变体已经出现在了 Flutter 社区 - 那就是 BLoC。BLoC 代表 Business Logic Components。BLoC 的主旨是 app 中的所有内容都应该表现为事件流:部分 widgets 发送事件;其他的 widgets 进行响应。BloC 位于中间,管理这些会话。Dart 甚至提供了处理流的语法,这些语法已经融入到了语言中。这种模式最好的地方是不需要导入任何插件,也不需要学习任何自定义语法。Flutter 本身已经包含了你需要的所有东西。在本教程里,你将创建一个 app,使用 Zomato 提供的 API 查找餐厅。在教程的结尾,这个 app 将完成下面的事情:使用 BLoC 模式封装 API 调用搜索餐厅并异步显示结果维护收藏列表,并在多个页面展示准备开始下载并使用你最喜欢的 IDE 打开 starter 项目工程。本教程将使用 Android Studio,如果你喜欢使用 Visual Studio Code 也完全可以。确保在命令行或 IDE 提示时运行 flutter packages get,以便下载最新版本的 http 包。这个 starter 项目工程包含一些基础的数据模型和网络文件。打开项目时,应该如下图所示:这里有3个文件用来和 Zomato 通信。获取 Zomato API Key在开始构建 app 之前,需要获取一个 API key。跳转到 Zomato 开发者页面 https://developers.zomato.com/api,创建一个账号,并产生一个新的 key。打开 DataLayer 目录下的 zomato_client.dart,修改类声明中的常量:class ZomatoClient {

final _apiKey = 'PASTE YOUR API KEY HERE';

...Note: 产品级 app 的最佳实践是,不要将 API key 存储在源码或 VCS(版本控制系统)中。最好是从一个配置文件中读取,配置文件在构建 app 时从其他地方引入。构建并运行这个工程,它将显示一个空白的界面。没有什么让人兴奋的,不是吗?是时候改变它了。让我们烤一个夹心蛋糕在写应用程序的时候,将类分层进行组织是非常重要的,无论是使用 Flutter 还是使用其他的什么框架。这更像是一种非正式的约定;并不是可以在代码中看到的具象的东西。每一层,或者一组类,负责一个具体的任务。starter 工程中有一个命名为 DataLayer 的目录,这个数据层负责应用程序的数据模型和与后端服务器的通信,但它对 UI 一无所知。每个项目工程都有轻微的不同,但总的来说,大体结构基本如下所示:这种架构约定与经典的 MVC 并没有太大的不同。 UI/Flutter 层只能与 BLoC 层通信。BLoC 层发送事件给数据层和 UI 层,同时处理业务逻辑。随着应用程序功能的不断增长,这种结构能够很好的进行扩展。深入剖析 BLoC流(stream),和 Future 一样,也是由 dart:async 包提供。流类似 Future,不同的是,Future 异步返回一个值,但流可以随着时间的推移生产多个值。如果 Future 是一个最终将被提供的值,那么流则是随着时间推移零星的提供的一系列的值。dart:async 包提供一个名叫 StreamController 的对象。StreamController 是实例化 stream 和 sink 的管理器对象。sink 是 stream 的对立面。stream 不断的产生输出,sink 不断的接收输入。总而言之,BLoCs 是这样一种实体,它们负责处理和存储业务逻辑,使用 sinks 接收输入数据,同时使用 stream 提供数据输出。位置页面在使用 app 找到适合吃饭的地方之前,需要告知 Zomato 你想在哪个地理位置就餐。在本章节,将创建一个简单的页面,包含一个头部搜索区域和一个展示搜索结果的列表。Note: 在输入这些代码示例之前,不要忘记打开 DartFmt 。它是保持 Flutter 应用程序代码风格的唯一方法。在工程的 lib/UI 目录下,创建一个名为 location_screen.dart 的新文件。在文件中添加一个 StatelessWidget 的扩展类,命名为 LocationScreen :import 'package:flutter/material.dart';

class LocationScreen extends StatelessWidget {

@override

Widget build(BuildContext context) {

return Scaffold(

appBar: AppBar(title: Text('Where do you want to eat?')),

body: Column(

children: [

Padding(

padding: const EdgeInsets.all(10.0),

child: TextField(

decoration: InputDecoration(

border: OutlineInputBorder(), hintText: 'Enter a location'),

onChanged: (query) { },

),

),

Expanded(

child: _buildResults(),

)

],

),

);

}

Widget _buildResults() {

return Center(child: Text('Enter a location'));

}

}位置页面包含一个 TextField,用户可以在这里输入地理位置信息。Note: 输入类时,IDE 会提示错误,这是因为这些类没有导入。要解决此问题,请将光标移到任何带有红色下划线的符号上,然后,在 macOS 上按 option+enter(在 Windows/Linux 上按 Alt+Enter)或单击红色灯泡。将会弹出一个菜单,在菜单中选择正确的文件进行导入。创建另外一个文件,main_screen.dart,用来管理 app 的页面流转。添加下面的代码到文件中:class MainScreen extends StatelessWidget {

@override

Widget build(BuildContext context) {

return LocationScreen();

}

}最后,更新main.dart以返回新页面。MaterialApp(

title: 'Restaurant Finder',

theme: ThemeData(

primarySwatch: Colors.red,

),

home: MainScreen(),

),构建并运行 app,看上去应该是这样:虽然比之前好了一些,但它仍然什么都做不了。是时候创建一些 BLoC 了。第一个 BLoC在 lib 目录下创建新的目录 BLoC,所有的 BLoC 类将放置到这里。在该目录下新建文件 bloc.dart,并添加如下代码:abstract class Bloc {

void dispose();

}所有的 BLoC 类都将遵循这个接口。这个接口里只有一个 dispose 方法。需要牢记的一点是,当不再需要流的时候,必须将其关闭,否则会产生内存泄漏。可以在 dispose 方法中检查和释放资源。第一个 BLoC 将负责管理 app 的位置选择功能。在 BLoC 目录,新建文件 location_bloc.dart, 添加如下代码:class LocationBloc implements Bloc {

Location _location;

Location get selectedLocation => _location;

// 1

final _locationController = StreamController();

// 2

Stream get locationStream => _locationController.stream;

// 3

void selectLocation(Location location) {

_location = location;

_locationController.sink.add(location);

}

// 4

@override

void dispose() {

_locationController.close();

}

}使用option+return导入基类的时候,选择第二个选项 -Import library package:restaurant_finder/BLoC/bloc.dart。对所有错误提示使用 option+return,直到所有依赖都被正确导入。LocationBloc 主要实现如下功能:声明了一个 private StreamController,管理 BLoC 的 stream 和 sink。StreamController 使用泛型告诉类型系统它将通过 stream 发送何种类型的对象。这行暴露了一个 public 的 getter 方法,调用者通过该方法获取 StreamController 的 stream。该方法是 BLoC 的输入,接收一个 Location 模型对象,将其缓存到私有成员属性 _location ,并添加到流的接收器(sink)中。最后,当这个 BLoC 对象被释放时,在清理方法中关闭 StreamController。否则 IDE 会提示 StreamController 存在内存泄漏。到目前为止,第一个 BLoC 已经完成,接下来创建一个查找位置的 BLoC。第二个 BLoC在 BLoC 目录中新建文件 location_query_bloc.dart,添加如下代码:class LocationQueryBloc implements Bloc {

final _controller = StreamController>();

final _client = ZomatoClient();

Stream> get locationStream => _controller.stream;

void submitQuery(String query) async {

// 1

final results = await _client.fetchLocations(query);

_controller.sink.add(results);

}

@override

void dispose() {

_controller.close();

}

}代码中的 //1 处,是 BLoC 输入端,该方法接收一个字符串类型参数,使用 start 工程中的 ZomatoClient 类从 API 获取位置信息。Dart 的 async /await 语法可以使代码更加简洁。结果返回后将其发布到流(stream)中。这个 BLoC 与上一个几乎相同,只是这个 BLoC 不仅存储和报告位置,还封装了一个 API 调用。将 BLoC 注入到 Widget Tree现在已经建立了两个 BLoC,需要一种方式将它们注入到 Flutter 的 widget 树。使用 provider 类型的 weidget 已成为Flutter的惯例。一个 provider 就是一个存储数据的 widget,它能够将数据很好的提供给它所有的子 widget。通常这是 InheritedWidget 的工作,但由于 BLoC 对象需要被释放,StatefulWidget 将提供相同的功能。虽然语法有点复杂,但结果是一样的。在 BLoC 目录下新建文件 bloc_provider.dart,并添加如下代码:// 1

class BlocProvider extends StatefulWidget {

final Widget child;

final T bloc;

const BlocProvider({Key key, @required this.bloc, @required this.child})

: super(key: key);

// 2

static T of(BuildContext context) {

final type = _providerType>();

final BlocProvider provider = findAncestorWidgetOfExactType(type);

return provider.bloc;

}

// 3

static Type _providerType() => T;

@override

State createState() => _BlocProviderState();

}

class _BlocProviderState extends State {

// 4

@override

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

// 5

@override

void dispose() {

widget.bloc.dispose();

super.dispose();

}

}代码解读如下:BlocProvider 是一个泛型类,泛型 T 被限定为一个实现了 BLoC 接口的对象。意味着这个 provider 只能存储 BLoC 对象。of 方法允许 widget tree 的子孙节点使用当前的 build context 检索 BlocProvider。在 Flutter 里这是非常常见的模式。这是获取泛型类型引用的通用方式。build 方法只是返回了 widget 的 child,并没有渲染任何东西。最后,这个 provider 继承自 StatefulWidget 的唯一原因是需要访问 dispose 方法。当 widget 从 widget tree 中移除,Flutter 将调用 dispose 方法,该方法将依次关闭流。对接位置页面现在已经完成了用于查找位置的 BLoC 层,下面将使用该层。首选,在 main.dart 文件里,在 material app 的上层放置一个 Location BLoC,用于存储应用状态。最简单的方法是,将光标移动到 MaterialApp 上方,按下 option+return (Windows/Linux 上是 Alt+Enter),在弹出的菜单中选择 Wrap with a new widget。Note: 此代码片段的灵感来自 Didier Boelens 的这篇精彩文章 Reactive Programming — Streams — BLoC。这个 widget 没有做任何优化,理论上是可以改进的。出于本文的目的,我们仍然使用这种简单的方法,它在大部分情况下完全可以接受。如果在 app 生命周期的后期发现它引起了性能问题,可以在 Flutter BLoC Package 中找到更全面的解决方案。使用 LocationBloc 类型的 BlocProvider 进行包装,并在 bloc 属性位置创建一个 LocationBloc 实例。return BlocProvider(

bloc: LocationBloc(),

child: MaterialApp(

title: 'Restaurant Finder',

theme: ThemeData(

primarySwatch: Colors.red,

),

home: MainScreen(),

),

);在 material app 的上层添加 widget,在 widget 里添加数据,这是在多个页面共享访问数据的好方式。在主界面 main_screen.dart 中需要做类似的事情。在 LocationScreen widget 上方点击 option+return,这次选择 ‘Wrap with StreamBuilder’。更新后的代码如下:return StreamBuilder(

// 1

stream: BlocProvider.of(context).locationStream,

builder: (context, snapshot) {

final location = snapshot.data;

// 2

if (location == null) {

return LocationScreen();

}

// This will be changed this later

return Container();

},

);StreamBuilder 是让 BLoC 模式如此美味的秘制酱汁。这些 widget 将自动监听来自 stream 的事件。当一个新的事件到达,builder 闭包函数将被执行来更新 widget tree。使用 StreamBuilder 和 BLoC 模式,在整个教程中都不需要调用 setState() 方法。在上面的代码中:对于 stream 属性,使用 of 方法获取 LocationBloc 并将其 stream 添加到 StreamBuilder 中。最初 stream 里没有数据,这是完全正常的。如果没有数据,返回 LocationScreen。否则,现在仅返回一个空白容器。下一步,使用之前创建的 LocationQueryBloc 更新 location_screen.dart 中的位置页面。不要忘记使用 IDE 提供的 widget 包装工具更轻松地更新代码。@override

Widget build(BuildContext context) {

// 1

final bloc = LocationQueryBloc();

// 2

return BlocProvider(

bloc: bloc,

child: Scaffold(

appBar: AppBar(title: Text('Where do you want to eat?')),

body: Column(

children: [

Padding(

padding: const EdgeInsets.all(10.0),

child: TextField(

decoration: InputDecoration(

border: OutlineInputBorder(), hintText: 'Enter a location'),

// 3

onChanged: (query) => bloc.submitQuery(query),

),

),

// 4

Expanded(

child: _buildResults(bloc),

)

],

),

),

);

}在这段代码里:首先,在 build 方法的开始部分实例化了一个新的 LocationQueryBloc 对象。将 BLoC 存储在 BlocProvider 中,BlocProvider 将管理 BLoC的生命周期。更新 TextField 的 onChanged 闭包方法,传递文本到 LocationQueryBloc。这将触发获取数据的调用链,首先调用 Zomato,然后将返回的位置信息发送到 stream 中。将 bloc 传递给 _buildResults 方法。在 LocationScreen 中添加一个 boolean 字段,用来跟踪这个页面是否是全屏对话框:class LocationScreen extends StatelessWidget {

final bool isFullScreenDialog;

const LocationScreen({Key key, this.isFullScreenDialog = false})

: super(key: key);

... 这个 boolean 字段仅仅是一个简单标志位(默认值为 false),稍后点击位置信息的时候,用来更新页面导航行为。现在更新 _buildResults 方法,添加一个 stream builder 并将结果显示在一个列表中。使用 ‘Wrap with StreamBuilder’ 快速更新代码。Widget _buildResults(LocationQueryBloc bloc) {

return StreamBuilder>(

stream: bloc.locationStream,

builder: (context, snapshot) {

// 1

final results = snapshot.data;

if (results == null) {

return Center(child: Text('Enter a location'));

}

if (results.isEmpty) {

return Center(child: Text('No Results'));

}

return _buildSearchResults(results);

},

);

}

Widget _buildSearchResults(List results) {

// 2

return ListView.separated(

itemCount: results.length,

separatorBuilder: (BuildContext context, int index) => Divider(),

itemBuilder: (context, index) {

final location = results[index];

return ListTile(

title: Text(location.title),

onTap: () {

// 3

final locationBloc = BlocProvider.of(context);

locationBloc.selectLocation(location);

if (isFullScreenDialog) {

Navigator.of(context).pop();

}

},

);

},

);

}在上面的代码中:stream 有三个条件分支,返回不同的结果。可能没有数据,意味着用户没有输入任何信息;可能是一个空的列表,意味着 Zomato 找不到任何你想要查找的内容;最后,可能是一个完整的餐厅列表,意味着每一件事都做的很完美。这里展示位置信息列表。这个方法的行为就是普通的声明式 Flutter 代码。在 onTap 闭包中,应用程序检索位于树根部的 LocationBloc,并告诉它用户已经选择了一个位置。点击列表项将会导致整个屏幕暂时变黑。继续构建并运行,该应用程序应该从 Zomato 获取位置结果并将它们显示在列表中。很好!这是真正的进步。餐厅页面这个 app 的第二个页面将根据搜索查询的结果显示餐厅列表。它也有自己的 BLoC 对象,用来管理页面状态。在 BLoC 目录下新建文件 restaurant_bloc.dart,添加下面的代码:class RestaurantBloc implements Bloc {

final Location location;

final _client = ZomatoClient();

final _controller = StreamController>();

Stream> get stream => _controller.stream;

RestaurantBloc(this.location);

void submitQuery(String query) async {

final results = await _client.fetchRestaurants(location, query);

_controller.sink.add(results);

}

@override

void dispose() {

_controller.close();

}

}代码几乎和 LocationQueryBloc 一样,唯一的不同是 API 和返回的数据类型。在 UI 目录下创建文件 restaurant_screen.dart,以使用新的 BLoC:class RestaurantScreen extends StatelessWidget {

final Location location;

const RestaurantScreen({Key key, @required this.location}) : super(key: key);

@override

Widget build(BuildContext context) {

return Scaffold(

appBar: AppBar(

title: Text(location.title),

),

body: _buildSearch(context),

);

}

Widget _buildSearch(BuildContext context) {

final bloc = RestaurantBloc(location);

return BlocProvider(

bloc: bloc,

child: Column(

children: [

Padding(

padding: const EdgeInsets.all(10.0),

child: TextField(

decoration: InputDecoration(

border: OutlineInputBorder(),

hintText: 'What do you want to eat?'),

onChanged: (query) => bloc.submitQuery(query),

),

),

Expanded(

child: _buildStreamBuilder(bloc),

)

],

),

);

}

Widget _buildStreamBuilder(RestaurantBloc bloc) {

return StreamBuilder(

stream: bloc.stream,

builder: (context, snapshot) {

final results = snapshot.data;

if (results == null) {

return Center(child: Text('Enter a restaurant name or cuisine type'));

}

if (results.isEmpty) {

return Center(child: Text('No Results'));

}

return _buildSearchResults(results);

},

);

}

Widget _buildSearchResults(List results) {

return ListView.separated(

itemCount: results.length,

separatorBuilder: (context, index) => Divider(),

itemBuilder: (context, index) {

final restaurant = results[index];

return RestaurantTile(restaurant: restaurant);

},

);

}

}新建一个独立的restaurant_tile.dart文件,用于显示餐厅的详细信息:class RestaurantTile extends StatelessWidget {

const RestaurantTile({

Key key,

@required this.restaurant,

}) : super(key: key);

final Restaurant restaurant;

@override

Widget build(BuildContext context) {

return ListTile(

leading: ImageContainer(width: 50, height: 50, url: restaurant.thumbUrl),

title: Text(restaurant.name),

trailing: Icon(Icons.keyboard_arrow_right),

);

}

}代码和位置页面的非常相似,几乎是一样的。唯一不同的是这里显示的是餐厅而不是位置信息。修改 main_screen.dart 文件中的 MainScreen,当得到位置信息后返回一个餐厅页面。builder: (context, snapshot) {

final location = snapshot.data;

if (location == null) {

return LocationScreen();

}

return RestaurantScreen(location: location);

},Hot restart 这个 app。选中一个位置,然后搜索想吃的东西,一个餐厅的列表会出现在你面前。看上去很美味。这是谁准备吃蛋糕了?收藏餐厅到目前为止,BLoC 模式已被用来管理用户输入,但远不止于此。假设用户想要跟踪他们最喜欢的餐厅并将其显示在单独的列表中。这也可以通过 BLoC 模式解决。在 BLoC 目录下为 BLoC 新建文件 favorite_bloc.dart,用于存储这个列表:class FavoriteBloc implements Bloc {

var _restaurants = [];

List get favorites => _restaurants;

// 1

final _controller = StreamController>.broadcast();

Stream> get favoritesStream => _controller.stream;

void toggleRestaurant(Restaurant restaurant) {

if (_restaurants.contains(restaurant)) {

_restaurants.remove(restaurant);

} else {

_restaurants.add(restaurant);

}

_controller.sink.add(_restaurants);

}

@override

void dispose() {

_controller.close();

}

}在 // 1 这里,BLoC 使用一个 Broadcast StreamController 代替常规的 StreamController。广播 stream 允许多个监听者,但常规 stream 只允许一个。前面两个 bloc 不需要广播流,因为只有一个一对一的关系。对于收藏功能,有两个地方需要同时监听 stream,所以广播在这里是需要的。Note: 作为通用规则,在设计 BLoC 的时候,应该优先使用常规 stream,当后面发现需要广播的时候,再将代码修改成使用广播 stream。当多个对象尝试监听同一个常规 stream 的时候,Flutter 会抛出异常。可以将此看作是需要修改代码的标志。这个 BLoC 需要从多个页面访问,意味着需要将其放置在导航器的上方。更新 main.dart 文件,再添加一个 widget,包裹在 MaterialApp 外面,并且在原来的 provider 里面。return BlocProvider(

bloc: LocationBloc(),

child: BlocProvider(

bloc: FavoriteBloc(),

child: MaterialApp(

title: 'Restaurant Finder',

theme: ThemeData(

primarySwatch: Colors.red,

),

home: MainScreen(),

),

),

);接下来在UI目录下新建文件favorite_screen.dart。这个 widget 将用于展示收藏的餐厅列表:class FavoriteScreen extends StatelessWidget {

@override

Widget build(BuildContext context) {

final bloc = BlocProvider.of(context);

return Scaffold(

appBar: AppBar(

title: Text('Favorites'),

),

body: StreamBuilder>(

stream: bloc.favoritesStream,

// 1

initialData: bloc.favorites,

builder: (context, snapshot) {

// 2

List favorites =

(snapshot.connectionState == ConnectionState.waiting)

? bloc.favorites

: snapshot.data;

if (favorites == null || favorites.isEmpty) {

return Center(child: Text('No Favorites'));

}

return ListView.separated(

itemCount: favorites.length,

separatorBuilder: (context, index) => Divider(),

itemBuilder: (context, index) {

final restaurant = favorites[index];

return RestaurantTile(restaurant: restaurant);

},

);

},

),

);

}

}在这个 widget 里:添加初始化数据到 StreamBuilder。StreamBuilder 将立即触发对 builder 闭包的执行,即使没有任何数据。这允许 Flutter 确保快照(snapshot)始终有数据,而不是毫无必要的重绘页面。检测 stream 的状态,如果这时还没有建立链接,则使用明确的收藏餐厅列表代替 stream 中发送的新事件。更新餐厅页面的 build 方法,添加一个 action,当点击事件触发时将收藏餐厅页面添加到导航栈中。@override

Widget build(BuildContext context) {

return Scaffold(

appBar: AppBar(

title: Text(location.title),

actions: [

IconButton(

icon: Icon(Icons.favorite_border),

onPressed: () => Navigator.of(context)

.push(MaterialPageRoute(builder: (_) => FavoriteScreen())),

)

],

),

body: _buildSearch(context),

);

}还需要一个页面,用来将餐厅添加到收藏餐厅中。在 UI 目录下新建文件 restaurant_details_screen.dart。这个页面大部分是静态的布局代码:class RestaurantDetailsScreen extends StatelessWidget {

final Restaurant restaurant;

const RestaurantDetailsScreen({Key key, this.restaurant}) : super(key: key);

@override

Widget build(BuildContext context) {

final textTheme = Theme.of(context).textTheme;

return Scaffold(

appBar: AppBar(title: Text(restaurant.name)),

body: Column(

crossAxisAlignment: CrossAxisAlignment.start,

children: [

_buildBanner(),

Padding(

padding: const EdgeInsets.all(8.0),

child: Column(

crossAxisAlignment: CrossAxisAlignment.start,

children: [

Text(

restaurant.cuisines,

style: textTheme.subtitle.copyWith(fontSize: 18),

),

Text(

restaurant.address,

style: TextStyle(fontSize: 18, fontWeight: FontWeight.w100),

),

],

),

),

_buildDetails(context),

_buildFavoriteButton(context)

],

),

);

}

Widget _buildBanner() {

return ImageContainer(

height: 200,

url: restaurant.imageUrl,

);

}

Widget _buildDetails(BuildContext context) {

final style = TextStyle(fontSize: 16);

return Padding(

padding: EdgeInsets.only(left: 10),

child: Row(

mainAxisAlignment: MainAxisAlignment.start,

children: [

Text(

'Price: ${restaurant.priceDisplay}',

style: style,

),

SizedBox(width: 40),

Text(

'Rating: ${restaurant.rating.average}',

style: style,

),

],

),

);

}

// 1

Widget _buildFavoriteButton(BuildContext context) {

final bloc = BlocProvider.of(context);

return StreamBuilder>(

stream: bloc.favoritesStream,

initialData: bloc.favorites,

builder: (context, snapshot) {

List favorites =

(snapshot.connectionState == ConnectionState.waiting)

? bloc.favorites

: snapshot.data;

bool isFavorite = favorites.contains(restaurant);

return FlatButton.icon(

// 2

onPressed: () => bloc.toggleRestaurant(restaurant),

textColor: isFavorite ? Theme.of(context).accentColor : null,

icon: Icon(isFavorite ? Icons.favorite : Icons.favorite_border),

label: Text('Favorite'),

);

},

);

}

}在上面代码中:这个 widget 使用收藏 stream 检测餐厅是否已被收藏,然后渲染适合的 widget。FavoriteBloc 中的 toggleRestaurant 方法的实现,使得 UI 不需要关心餐厅的状态。如果餐厅不在收藏列表中,它将会被添加进来;反之,如果餐厅在收藏列表中,它将会被删除。在 restaurant_tile.dart 文件中添加 onTap 闭包,用来将这个新的页面添加到 app 中。onTap: () {

Navigator.of(context).push(

MaterialPageRoute(

builder: (context) =>

RestaurantDetailsScreen(restaurant: restaurant),

),

);

},构建并运行这个 app。用户应该可以收藏、取消收藏和查看收藏列表了。甚至可以从收藏餐厅页面中删除餐厅,而无需添加额外的代码。这就是流(stream)的力量!更新位置信息如果用户想更改他们正在搜索的位置怎么办?现在的代码实现,如果想更改位置信息,必须重新启动这个 app。因为已经将 app 的工作设置为基于一系列的流,所以添加这个功能简直不费吹灰之力的。甚至就像是在蛋糕上放一颗樱桃一样简单!在餐厅页面添加一个 floating action button,并将位置页面以模态方式展示: ...

body: _buildSearch(context),

floatingActionButton: FloatingActionButton(

child: Icon(Icons.edit_location),

onPressed: () => Navigator.of(context).push(MaterialPageRoute(

builder: (context) => LocationScreen(

// 1

isFullScreenDialog: true,

),

fullscreenDialog: true)),

),

);

}在 // 1 处,设置 isFullScreenDialog 的值为 true。这是我们之前添加到位置页面的。之前在为 LocationScreen 编写的 ListTile 中,添加 onTap 闭包时使用过这个标志。onTap: () {

final locationBloc = BlocProvider.of(context);

locationBloc.selectLocation(location);

if (isFullScreenDialog) {

Navigator.of(context).pop();

}

},这样做的原因是,如果位置页面是以模态方式展现的,需要将它从导航栈中移除。如果没有这个代码,当点击 ListTile 时,什么都不会发生。位置信息 stream 将被更新,但 UI 不会有任何响应。最后一次构建并运行这个 app。你将看到一个 floating action button,当点击该按钮时,将以模态方式展示位置页面。然后去哪?恭喜你掌握了 BLoC 模式。 BLoC 是一种简单但功能强大的模式,可以帮助你轻松驯服 app 的状态管理,因为它可以在 widget tree 上上下飞舞。可以在本教程的 Download Materials 中找到最终的示例项目工程,如果想运行最终的示例项目,需要先把你的 API key 添加到 zomato_client.dart。其他值得一看的架构模式有:Provider - https://pub.dev/packages/providerScoped Model - https://pub.dev/packages/scoped_modelRxDart - https://pub.dev/packages/rxdartRedux - https://pub.dev/packages/redux同时请查阅 流 (stream) 的官方文档,和关于 BLoC 模式的 Google IO 讨论。希望你喜欢本 Flutter BLoC 教程。与往常一样,如果有任何问题或意见,请随时联系我,或者在下面评论!编辑于 2022-01-24 12:44Flutter设计模式状态管理​赞同 7​​4 条评论​分享​喜欢​收藏​申请

Flutter 入门与实战(八十五):GitHub 高赞的BLoC 简介 - 掘金

Flutter 入门与实战(八十五):GitHub 高赞的BLoC 简介 - 掘金

首页 首页

沸点

课程

直播

活动

竞赛

商城

APP

插件 搜索历史

清空

创作者中心

写文章 发沸点 写笔记 写代码 草稿箱 创作灵感

查看更多

会员

登录

注册

Flutter 入门与实战(八十五):GitHub 高赞的BLoC 简介

岛上码农

2021-09-30

3,413

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

前言

在 Flutter 的状态管理插件中,BLoC(Business Logic Component)非常受欢迎,事实上在 GitHub 上,BLoC 在众多的状态管理插件中的 Star 是最多的( 共7.8k,Provider 是3.9k,GetX 是4.6k)。这主要的原因是 BLoC 更多的是一种设计模式,按照这种设计模式可以转变为很多种状态管理实现。实际上在 pub 搜索 BLoC 会出现很多相关的插件,当然,官方的还是 bloc 和 flutter_bloc 组合。我们本篇先来介绍一下 BLoC 的基本概念。

BLoC 与 Stream

BLoC 依赖 Stream和 StreamController实现,组件通过Sinks发送更新状态的事件,然后再通过 Streams 通知其他组件更新。事件处理和通知刷新的业务逻辑都是由 BLoC 完成,从而实现业务逻辑与 UI 层的分离(有点类似 Redux),并且逻辑部分可以复用和可以单独进行单元测试。

Flutter 中的 BLoC

bloc package是为了快速在 Flutter 或 Dart 中实现 BLoC 模式的插件,BLoC 官网提供了很多示例和详细的文档,有兴趣深入了解的可以上官网啃英文文档和浏览示例项目的代码。在 BLoC 有三个重要的概念,分别是 Cubit、 BlocObserver和BLoC

Cubit

Cubit是管理状态数据的 BlocBase 子类,它可以管理任意类型的数据,包括基本类型到复杂对象。在Cubit调用 emit 构建新的状态数据前需要给状态数据一个初始值。当状态数据发生改变的时候,会触发 Cubit 的 onChange 回调,如果出现错误则会触发 onError 回调。

class CounterCubit extends Cubit {

CounterCubit() : super(0);

void increment() => emit(state + 1);

void decrement() => emit(state - 1);

@override

void onChange(Change change) {

super.onChange(change);

print(change);

}

@override

void onError(Object error, StackTrace stackTrace) {

print('$error, $stackTrace');

super.onError(error, stackTrace);

}

}

UI界面可以通过调用 Cubit 对外暴露的更新状态方法触发状态更新,而在 onChange 中会得到更新前后的状态,从而可以触发界面刷新。

BlocObserver

BlocObserver 可以监听所有的 Cubit 的变化,从而使得我们可以同时监听多个 Cubit。例如运行下面的代码

class CounterCubit extends Cubit {

CounterCubit({initial = 0}) : super(initial);

void increment() => emit(state + 1);

void decrement() => emit(state - 1);

}

class MyBlocObserver extends BlocObserver {

@override

void onCreate(BlocBase bloc) {

print('BloC Observer onCreate: ${bloc.state}');

super.onCreate(bloc);

}

@override

void onChange(BlocBase bloc, Change change) {

print('BloC Observer onChange: $change');

super.onChange(bloc, change);

}

@override

void onClose(BlocBase bloc) {

print('BloC Observer onClose: ${bloc.state}');

super.onClose(bloc);

}

@override

void onError(BlocBase bloc, Object error, StackTrace stackTrace) {

print('Bloc Observer onError: $error, $stackTrace');

super.onError(bloc, error, stackTrace);

}

@override

void onEvent(Bloc bloc, Object? event) {

print('Bloc Observer onEvent: $event, ${bloc.state}');

super.onEvent(bloc, event);

}

}

void main() {

Bloc.observer = MyBlocObserver();

final cubit = CounterCubit();

cubit.increment();

print('after increment: ${cubit.state}');

cubit.decrement();

print('after decrement: ${cubit.state}');

final anotherCubit = CounterCubit();

anotherCubit.increment();

cubit.close();

anotherCubit.close();

}

控制台打印结果为:

BloC Observer onCreate: 0

BloC Observer onChange: Change { currentState: 0, nextState: 1 }

BloC Observer onChange: Change { currentState: 1, nextState: 0 }

BloC Observer onCreate: 10

BloC Observer onChange: Change { currentState: 10, nextState: 11 }

BloC Observer onClose: 0

BloC Observer onClose: 11

Bloc

Bloc也是继承 BlocBase 的类,但相比 Cubit 来说更为高级一些。它使用的是 events 而不是暴露的函数来更新状态。

在 Bloc 内部,有一个onEvent 方法,在这个方法里,通过 EventTransformer将 event转换为更新状态的方法来刷新状态数据。每个event都可以有对应的 EventHandler来处理该 event,完成后再通过 emit 触发通知状态更新。当状态转变前会调用 onTransition,在这里会有当前的状态,触发更新的 event 和下一个状态。

用 Bloc 改写之前的 CounterCubit 形式如下,由于 Bloc 也是 BlocBase 的子类,因此也可以使用 Observer 监测它的变化。

abstract class CounterEvent {}

class IncrementEvent extends CounterEvent {}

class DecrementEvent extends CounterEvent {}

class CounterBloc extends Bloc {

CounterBloc(int initialState) : super(initialState) {

on((event, emit) => emit(state + 1));

on((event, emit) => emit(state - 1));

}

@override

void onTransition(Transition transition) {

print(

'Current: ${transition.currentState}, Next: ${transition.nextState}, Event: ${transition.event}');

super.onTransition(transition);

}

}

void main() {

Bloc.observer = MyBlocObserver();

final counterBloc = CounterBloc(5);

counterBloc.add(IncrementEvent());

counterBloc.add(DecrementEvent());

counterBloc.close();

}

执行结果如下:

Current: 5, Next: 6, Event: Instance of 'IncrementEvent'

BloC Observer onChange: Change { currentState: 5, nextState: 6 }

Current: 6, Next: 5, Event: Instance of 'DecrementEvent'

BloC Observer onChange: Change { currentState: 6, nextState: 5 }

BloC Observer onClose: 5

总结

从 Bloc 的设计来看,使用了函数(Cubit形式)和事件( Bloc形式)的方式来驱动状态数据更新,然后再通过emit通知状态更新。通过这种方式解耦 UI 界面和业务逻辑。可以看到,Bloc 本身的业务逻辑和界面完全无关,这使得我们可以直接编写测试代码,而无需依赖界面,如同本篇的 main 方法中的代码其实就可以作为单元测试代码来验证业务逻辑是否正确。这使得 Bloc 构建的应用程序的可维护性会更好。

我是岛上码农,微信公众号同名,这是Flutter 入门与实战的专栏文章,提供体系化的 Flutter 学习文章。对应源码请看这里:Flutter 入门与实战专栏源码。如有问题可以加本人微信交流,微信号:island-coder。

:觉得有收获请点个赞鼓励一下!

:收藏文章,方便回看哦!

:评论交流,互相进步!

岛上码农

公众号 @岛上码农 | 掘金签约作者

261

文章

1.0m

阅读

2.3k

粉丝 目录 收起

前言

BLoC 与 Stream

Flutter 中的 BLoC

Cubit

BlocObserver

Bloc

总结

友情链接:

历史大唐小说

vue 搜索历史记录

大蛇的新娘人与蛇婚后生存

膳食设计和营养管理的区别

大学生人文社科知识读本百度云

孙作宾画册

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

ter中的BLoC,你所需要知道的一切 - 简书登录注册写文章首页下载APP会员IT技术Flutter中的BLoC,你所需要知道的一切进击小岛关注赞赏支持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();

Stream 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 {

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(

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。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 {

CounterBloc() : super(0);

@override

Stream 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: [

BlocBuilder(

builder: (context, state) {

return Text(

'$state',

style: TextStyle(fontSize: 24.0),

);

},

),

SizedBox(height: 20.0),

Row(

mainAxisAlignment: MainAxisAlignment.center,

children: [

FloatingActionButton(

onPressed: () {

BlocProvider.of(context).add(1);

},

child: Icon(Icons.add),

),

SizedBox(width: 20.0),

FloatingActionButton(

onPressed: () {

BlocProvider.of(context).add(-1);

},

child: Icon(Icons.remove),

),

],

),

],

),

),

);

}

}

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

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

高级功能

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

多Bloc的使用

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

MultiBlocProvider(

providers: [

BlocProvider(

create: (context) => CounterBloc(),

),

BlocProvider(

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 get props => [count, text];

}

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

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

class MyBloc extends Bloc {

MyBloc() : super(MyState(count: 0, text: ''));

@override

Stream 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(

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类,可以更好地管理复杂的状态,并提高应用程序的可维护性。

最后编辑于 :2024.03.06 10:46:18©著作权归作者所有,转载或内容合作请联系作者人面猴序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...沈念sama阅读 145,261评论 1赞 308死咒序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...沈念sama阅读 62,177评论 1赞 259救了他两次的神仙让他今天三更去死文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...开封第一讲书人阅读 96,329评论 0赞 214道士缉凶录:失踪的卖姜人 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...开封第一讲书人阅读 41,490评论 0赞 184港岛之恋(遗憾婚礼)正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...茶点故事阅读 49,353评论 1赞 262恶毒庶女顶嫁案:这布局不是一般人想出来的文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...开封第一讲书人阅读 39,028评论 1赞 179城市分裂传说那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...沈念sama阅读 30,611评论 2赞 276双鸳鸯连环套:你想象不到人心有多黑文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...开封第一讲书人阅读 29,383评论 0赞 171万荣杀人案实录序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...沈念sama阅读 32,749评论 0赞 215护林员之死正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...茶点故事阅读 29,460评论 2赞 219白月光启示录正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...茶点故事阅读 30,814评论 1赞 232活死人序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...沈念sama阅读 27,255评论 2赞 215日本核电站爆炸内幕正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...茶点故事阅读 31,752评论 3赞 214男人毒药:我在死后第九天来索命文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...开封第一讲书人阅读 25,685评论 0赞 9一桩弑父案,背后竟有这般阴谋文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...开封第一讲书人阅读 26,114评论 0赞 170情欲美人皮我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...沈念sama阅读 33,747评论 2赞 234代替公主和亲正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...茶点故事阅读 33,901评论 2赞 238推荐阅读更多精彩内容Flutter | 状态管理探索篇——BLoC前言 Flutter的很多灵感来自于React,它的设计思想是数据与视图分离,由数据映射渲染视图。所以在Flutt...zhx喜籽阅读 1,064评论 0赞 2flutter_bloc使用解析---骚年,你还在手搭bloc吗!flutter_bloc使用将从下图的三个维度说明 前言 首先,有很多的文章在说flutter bloc模式的应用...小呆呆666阅读 1,971评论 2赞 9Flutter实践:深入探索 flutter 中的状态管理方式(1)利用 Flutter 内置的许多控件我们可以打造出一款不仅漂亮而且完美跨平台的 App 外壳,我利用其特性完成了类...Meandni阅读 1,835评论 1赞 9flutter bloc使用方法以及源码解析flutter bloc, provider和getx异同点 一、bloc,环境配置 pubspec.yaml 为...醉了俗身醒了初心阅读 665评论 0赞 0Flutter - BLoC 第一讲本篇已同步到 个人博客 ,欢迎常来。 【译文】Reactive Programming - Streams - ...SilenceZhou阅读 14,872评论 0赞 32评论0赞赞1赞赞赏更

Bloc State Management Library | Bloc

c State Management Library | Bloc

Skip to content Bloc Search Cancel GitHub Discord Select theme DarkLightAuto Select language EnglishAzərbaycanČeštinaDeutschEspañolFilipinoFrançais日本語한국인PortuguêsРусский简体中文 ✨ Visit the

Bloc Shop ✨

Bloc v8.1.3 A predictable state management library for Dart. Get Started View on GitHub Sponsored with by Become a Sponsor

Get Started Terminal window# Add bloc to your project.dart pub add blocOur getting started guide has step-by-step instructions on how to start using Bloc in just a few minutes. Take a guided tour Complete the official tutorials to learn best

practices and build a variety of different apps powered by Bloc. Build with Bloc Explore high quality, fully tested sample

apps like the counter,

timer, infinite list, weather, todo and more! Learn

Why Bloc?

Core Concepts

Architecture

Testing

Naming Conventions

Integrations

VSCode Integration

IntelliJ Integration

Mason CLI Integration

Custom Templates

Developer Tools

Join our Discord

bloc是什么意思_bloc的翻译_音标_读音_用法_例句_爱词霸在线词典

是什么意思_bloc的翻译_音标_读音_用法_例句_爱词霸在线词典首页翻译背单词写作校对词霸下载用户反馈专栏平台登录bloc是什么意思_bloc用英语怎么说_bloc的翻译_bloc翻译成_bloc的中文意思_bloc怎么读,bloc的读音,bloc的用法,bloc的例句翻译人工翻译试试人工翻译翻译全文简明柯林斯牛津blocCET6/IELTS英 [blɒk]美 [blɑːk]释义n.(政治利益一致的)国家集团; 共同体大小写变形:BLOC点击 人工翻译,了解更多 人工释义词态变化复数: blocs;实用场景例句全部集团联盟...the former Soviet bloc.前苏联集团柯林斯高阶英语词典...the world's largest trading bloc.世界上最大的贸易共同体柯林斯高阶英语词典The Bloc Quebecois's associate health critic was also unhappy with the decision.魁北克人党的医疗卫生评论家也对此项决定感到不满.期刊摘选The Liberal Bloc voted for the tax increase.开明集团投票赞成增税.《简明英汉词典》Using a Chemtrol spanner wrench, unscrew the seat - carrier ( Tru - Bloc Model - C ) by rotating in the counterclockwise direction.使用一把Chemtrol活动扳手, 通过逆时针方向旋转拧松阀座支架 ( Tru -Bloc型号C ) 的螺纹.期刊摘选After 1989, peace broke out in the former East Bloc.1989年后, 和平在以前的东部国家爆发.期刊摘选This regional bloc has fallen apart.这一地区性集团已土崩瓦解.《现代汉英综合大词典》Could east Africa take off as a regional trading bloc?东非可以腾飞发展成一个区域贸易集团 吗 ?期刊摘选However, en bloc resection is often not achieved using conventional methods.然而, 应用传统的EMR技术,很难将肿瘤组织整块切除.期刊摘选A solid bloc of union members support the decision.工会会员团结起来支持该决定.《简明英汉词典》The five countries formed a regional trade bloc.5国成立了一个地区性贸易集团.《简明英汉词典》But trade is one area where the Union has enormous powers and as a single bloc.但是贸易是欧盟的力量,并且是欧盟是作为一体的.期刊摘选The farm bloc in the U.S. Senate.美国参议院农业集团.期刊摘选I represent the Fasten Steel Bloc. Company , and welcome to Jiangyin.我代表 法尔胜集团 欢迎你来江阴.英汉 - 翻译样例 - 口语We must consider all the difficulties en bloc.我们必须从整体上考虑所有这些困难.辞典例句Including Japan, Latin America and the former soviet bloc , almost half the world economy was affected.包括日本, 拉美, 俄罗斯在内的世界经济的一半都受到了影响.期刊摘选There have been growing tensions within the trading bloc.贸易同盟国的关系越来越紧张.《简明英汉词典》All economic and technological aid to the Communist Bloc , including the PLO, will be terminated immediately.所有共产党支部——包括巴勒斯坦解放组织(PLO) —— 将会立刻被解除所有援助.期刊摘选In its own interests, this bloc will not enter the war for the time being.这个集团,为了自己的利益, 暂时还不至于转入战争.期刊摘选But the regional bloc still says it will not interfere in Burmas domestic affairs.不过,东盟仍然表示不会干预缅甸的内部事务.期刊摘选The Maoists are the biggest bloc in Nepal's parliament, although not an absolute majority.毛派虽然不是尼泊尔议会中的绝对多数, 但确是当中最大的集团.期刊摘选There are reports of teachers resigning en bloc.有一些关于教师集体辞职的报道。《牛津高阶英汉双解词典》An enlarged Latin group would be in a stronger position to negotiate en bloc with the United States.壮大的拉丁国家群体抱团和美国谈判将处于更加强势的位置。柯林斯例句Now the governors en bloc are demanding far more consultation and rights over contractual approval.现在,董事们一致要求进行更多的磋商以及在合同审批方面的权利。柯林斯例句We think lower German interest rates in due course will allow European currencies to depreciate against the dollar bloc.我们认为较低的德国利率到时候将容许欧洲货币相对于美元集团货币发生贬值。柯林斯例句The selectors should resign en bloc.选拔人应该集体辞职。柯林斯例句收起实用场景例句英英释义Noun1. a group of countries in special alliance收起英英释义同义词combinationfactioncoalition释义词态变化实用场景例句英英释义

Flutter | 状态管理探索篇——BLoC(三) - 掘金

Flutter | 状态管理探索篇——BLoC(三) - 掘金

首页 首页

沸点

课程

直播

活动

竞赛

商城

APP

插件 搜索历史

清空

创作者中心

写文章 发沸点 写笔记 写代码 草稿箱 创作灵感

查看更多

会员

登录

注册

Flutter | 状态管理探索篇——BLoC(三)

Vadaski

2018-10-10

41,685

前言

Flutter的很多灵感来自于React,它的设计思想是数据与视图分离,由数据映射渲染视图。所以在Flutter中,它的Widget是immutable的,而它的动态部分全部放到了状态(State)中。

在之前的文章中,我们已经介绍了scoped model与redux两种状态管理方案在flutter中的应用。他们似乎都还不错,但都还是美中不足。今天我将介绍Google提出的一种全新的解决方案——BLoC。

在正式开始介绍前,我希望您已经阅读并理解了stream的相关知识,后面的内容都基于此。如果您还未了解过dart:stream 的话,我建议您先阅读这篇文章:Dart:什么是Stream。

BLoC

为什么需要状态管理

我们一直在找寻强大的状态管理方式。也许你并没有想过,flutter自身已经为我们提供了状态管理,而且你经常都在用到。

没错,它就是 Stateful widget。当我们接触到flutter的时候,首先需要了解的就是有些小部件是有状态的,有些则是无状态的。stateless widget 与 stateful widget。

在stateful widget中,我们widget的描述信息被放进了State,而stateful widget只是持有一些immutable的数据以及创建它的状态而已。它的所有成员变量都应该是final的,当状态发生变化的时候,我们需要通知视图重新绘制,这个过程就是setState。

这看上去很不错,我们改变状态的时候setState一下就可以了。

在我们一开始构建应用的时候,也许很简单,我们这时候可能并不需要状态管理。

但是随着功能的增加,你的应用程序将会有几十个甚至上百个状态。这个时候你的应用应该会是这样。

一旦当app的交互变得复杂,setState出现的次数便会显著增加,每次setState都会重新调用build方法,这势必对于性能以及代码的可阅读性带来一定的影响。

能不能不使用setState就能刷新页面呢?如何在多个页面中共享状态?我们希望有一种更加强大的方式,来管理我们的状态。

BLoC是什么

BLoC是一种利用reactive programming方式构建应用的方法,这是一个由流构成的完全异步的世界。

用StreamBuilder包裹有状态的部件,streambuilder将会监听一个流

这个流来自于BLoC

有状态小部件中的数据来自于监听的流。

用户交互手势被检测到,产生了事件。例如按了一下按钮。

调用bloc的功能来处理这个事件

在bloc中处理完毕后将会吧最新的数据add进流的sink中

StreamBuilder监听到新的数据,产生一个新的snapshot,并重新调用build方法

Widget被重新构建

BLoC能够允许我们完美的分离业务逻辑!再也不用考虑什么时候需要刷新屏幕了,一切交给StreamBuilder和BLoC!和StatefulWidget说拜拜!!

BLoC代表业务逻辑组件(Business Logic Component),由来自Google的两位工程师 Paolo Soares和Cong Hui设计,并在2018年DartConf期间(2018年1月23日至24日)首次展示。点击观看Youtube视频。。

Lets do it!

这里我们以一个最简单的CountApp举例。简单介绍BLoC的用法。该项目完整代码已上传Github。

这是一个在不同页面使用BLoC共享状态信息的app。这两个页面都依赖于一个数字,这个数字会随着我们按下按钮的次数而增加。

第一步:创建BLoC

我们这里的要求很简单,仅仅只是输出一个数字而已,然后有一个方法能够让数字加一。所以我们需要创建一条能够通过int类型数据的流。

import 'dart:async';

class CountBLoC {

int _count;

StreamController _countController;

CountBLoC() {

_count = 0;

_countController = StreamController();

}

Stream get value => _countController.stream;

increment() {

_countController.sink.add(++_count);

}

dispose() {

_countController.close();

}

}

为什么要使用私有变量“_”

一个应用需要大量开发人员参与,你写的代码也许在几个月之后被另外一个开发看到了,这时候假如你的变量没有被保护的话,也许同样是让count++,他会用countController.sink.add(++_count)这种方法,而不是调用 increment方法。

虽然两种方式的效果完全一样,但是第二种方式将会让我们的business logic零散的混入其他代码中,提高了代码耦合程度,非常不利于代码的维护以及阅读,所以为了让BLoC完全分离我们的业务逻辑,请务必使用私有变量。

第二步:创建BLoC实例

这里有三种方式创建bloc

全局单例创建

局部创建

scoped

由于我们需要在两个屏幕中访问同一个bloc,所以我们只能选择全局单例模式或者scoped模式。

全局单例模式

全局单例我们只需要在bloc类的文件中创建一个bloc实例即可。不过我并不推荐这种做法,因为不需要用这个bloc的时候,我们应该释放它。

但是为了让我解释的尽量简单,后面我将会基于全局单例模式来介绍。

Scoped模式

创建一个bloc provider类,这里我们需要借助InheritWidget,实现of方法并让updateShouldNotify返回true。

class BlocProvider extends InheritedWidget {

CountBLoC bLoC = CountBLoC();

BlocProvider({Key key, Widget child}) : super(key: key, child: child);

@override

bool updateShouldNotify(_) => true;

static CountBLoC of(BuildContext context) =>

(context.inheritFromWidgetOfExactType(BlocProvider) as BlocProvider).bLoC;

}

小提示: 这里updateShouldNotify需要传入一个InheritedWidget oldWidget,但是我们强制返回true,所以传一个“_”占位。

第三步:在页面中使用StreamBuilder

这里以第一个页面为例,仅仅显示文字+数字。

StreamBuilder(

stream: bloc.value,

initialData: 0,

builder: (BuildContext context, AsyncSnapshot snapshot) {

return Text(

'You hit me: ${snapshot.data} times',

style: Theme.of(context).textTheme.display1,

);

})

StreamBuilder中stream参数代表了这个stream builder监听的流,我们这里监听的是countBloc的value(它是一个stream)。

initData代表初始的值,因为当这个控件首次渲染的时候,还未与用户产生交互,也就不会有事件从流中流出。所以需要给首次渲染一个初始值。

builder函数接收一个位置参数BuildContext 以及一个snapshot。snapshot就是这个流输出的数据的一个快照。我们可以通过snapshot.data访问快照中的数据。也可以通过snapshot.hasError判断是否有异常,并通过snapshot.error获取这个异常。

StreamBuilder中的builder是一个AsyncWidgetBuilder,它能够异步构建widget,当检测到有数据从流中流出时,将会重新构建。

在第二个页面中调用increment

floatingActionButton: FloatingActionButton(

onPressed: ()=> bloc.increment(),

child: Icon(Icons.add),

)

由于这里并不涉及widget的重构,我们只需要调用bloc的功能即可。

处理广播流

我们构建好ui后,运行程序将会发现这件奇怪的事。

第二个页面的数字无法显示,而且控制台抛出了这个异常。

flutter: Bad state: Stream has already been listened to.

这是由于流被重复监听导致的。 两个页面中都需要显示这个数字,那么就使用了两个StreamBuilder。而StreamBuilder都监听的同一个流,所以导致了流被重复监听了。

还记得我们在Dart|什么是Stream中说的两种流吗。没错,我们创建StreamController的时候,默认是创建的单订阅流。所以我们需要将流改成广播流。

_countController = StreamController.broadcast();

只需要在创建StreamController的时候调用broadcast方法即可。

来看看效果

但是我们这里还有一个小问题,你发现了吗。

Q&A

为什么第二次进入UnderPage的时候,计数器显示为0,按了一下才好

这是由于我们在第一次pop UnderPage的时候,这个页面已经被销毁了。当我们再push进去的时候,StreamBuilder无法收听到最后一次事件(已经流过去了),只能显示initiaData。而再次点击时,正确的数字被add进了流,StreamController收听到了它,所以又能显示正确的数据了。

这个问题能够解决吗?

答案是肯定的,使用rxdart!rxdart极大的增强了流的功能,解决方法将会在后续rxdart篇介绍。

大型应用中应该如何组织BLoC

大型应用程序需要多个BLoC。一个好的模式是为每个屏幕使用一个顶级组件,并为每个复杂足够的小部件使用一个。但是,太多的BLoC会变得很麻烦。此外,如果您的应用中有数百个可观察量(流),则会对性能产生负面影响。换句话说:不要过度设计你的应用程序。

——Filip Hracek

一个更加复杂的app

filip提供了一个更复杂的BLoC样本。他将购物应用程序重新创建为一个更现实的例子,其中产品目录逐页从网络中获取,我们有无限的这些产品列表。此外,对于目录中的每个产品,我们希望在产品已在目录中时稍微更改ProductSquare的显示。

了解更多

下面有一些优秀的文章能够给您更多参考

[译]Flutter响应式编程:Streams和BLoC

Build reactive mobile apps in Flutter — companion article

Technical Debt and Streams/BLoC (The Boring Flutter Development Show, Ep. 4)

Using the BloC Pattern to Build Reactive Applications with Streams in Dart's Flutter Framework

写在最后

本次所用到的代码已经上传Github:github.com/OpenFlutter…

bloc是一个优秀的状态管理方式,它能够帮助我们更好的构建复杂的大型应用。但是他还不是完美的(至少目前不是)。它在处理大量异步事件以及分离业务逻辑上表现很优秀,但是在共享状态上还有一些缺陷。

有人尝试将redux与bloc结合使用,试图找到突破口。这里有一个专门为它编写的库:rebloc。感兴趣的朋友可以自行了解一下。

如果你在使用bloc进行状态管理的时候有任何好的点子,或者是疑问,欢迎在下方评论区以及我的邮箱1652219550a@gmail.com留言,我会在24小时内与您联系!

下一篇文章将会为大家介绍Reactive Programming的最佳库RxDart在BLoC上的实践,敬请期待。

Vadaski

终端开发工程师 @CFUG 成员 / @DiDi

44

文章

480k

阅读

10k

粉丝 目录 收起

前言

BLoC

为什么需要状态管理

BLoC是什么

Lets do it!

第一步:创建BLoC

为什么要使用私有变量“_”

第二步:创建BLoC实例

全局单例模式

Scoped模式

第三步:在页面中使用StreamBuilder

在第二个页面中调用increment

处理广播流

来看看效果

Q&A

为什么第二次进入UnderPage的时候,计数器显示为0,按了一下才好

大型应用中应该如何组织BLoC

一个更加复杂的app

了解更多

写在最后

友情链接:

剑齿虎模拟器

醉爱小说

假神仙 小说

灯火可亲表达了什么情感

回到大明当明君

男主有点痞的校园小说

Flutter 状态管理之BLoC - ifgyong - 博客园

Flutter 状态管理之BLoC - ifgyong - 博客园

会员

周边

新闻

博问

AI培训

云市场

所有博客

当前博客

我的博客

我的园子

账号设置

简洁模式 ...

退出登录

注册

登录

ifgyong

博客园

首页

新随笔

联系

订阅

管理

Flutter 状态管理之BLoC

在正式介绍 BLoC之前, 为什么我们需要状态管理。如果你已经对此十分清楚,那么建议直接跳过这一节。如果我们的应用足够简单,Flutter 作为一个声明式框架,你或许只需要将 数据 映射成 视图 就可以了。你可能并不需要状态管理,就像下面这样。

但是随着功能的增加,你的应用程序将会有几十个甚至上百个状态。这个时候你的应用应该会是这样。我们很难再清楚的测试维护我们的状态,因为它看上去实在是太复杂了!而且还会有多个页面共享同一个状态,例如当你进入一个文章点赞,退出到外部缩略展示的时候,外部也需要显示点赞数,这时候就需要同步这两个状态。Flutter 实际上在一开始就为我们提供了一种状态管理方式,那就是 StatefulWidget。但是我们很快发现,它正是造成上述原因的罪魁祸首。在 State 属于某一个特定的 Widget,在多个 Widget 之间进行交流的时候,虽然你可以使用 callback 解决,但是当嵌套足够深的话,我们增加非常多可怕的垃圾代码。这时候,我们便迫切的需要一个架构来帮助我们理清这些关系,状态管理框架应运而生。

 

BLoC 是什么

旨在使用Widget更加加单,更加快捷,方便不同开发者都能使用,可以记录组件的各种状态,方便测试,让许多开发者遵循相同的模式和规则在一个代码库中无缝工作。

如何使用

简单例子

老规矩,我们写一个增加和减小的数字的例子,首先定义一个存储数据的Model,我们继承Equtable来方便与操作符==的判断,Equtable实现了使用props是否相等来判断两个对象是否相等,当然我们也可以自己重写操作符==来实现判断两个对象是否相等。

自己实现操作符如下:

  

@override

bool operator ==(Object other) {

if (other is Model)

return this.count == other.count &&

age == other.count &&

name == other.name;

return false;

}

 

 

使用Equtable操作符==关键代码如下:

// ignore: must_be_immutable

class Model extends Equatable {

int count;

int age;

String name;

List list;

Model({this.count = 0, this.name, this.list, this.age = 0});

@override

List get props => [count, name, list, age];

Model addCount(int value) {

return clone()..count = count + value;

}

Model addAge(int value) {

return clone()..age = age + value;

}

Model clone() {

return Model(count: count, name: name, list: list, age: age);

}

}

 

 

构造一个装载Model数据的Cubit:

class CounterCubit extends Cubit {

CounterCubit() : super(Model(count: 0, name: '老王'));

void increment() {

print('CounterCubit +1');

emit(state.addCount(1));

}

void decrement() {

print('CounterCubit -1');

emit(state.clone());

}

void addAge(int v) {

emit(state.addAge(v));

}

void addCount(int v) {

emit(state.addCount(v));

}

}

 

 

数据准备好之后准备展示了,首先在需要展示数据小部件上层包裹一层BlocProvider,关键代码:

BlocProvider(

create: (_) => CounterCubit(),

child: BaseBLoCRoute(),

)

 

 

要是多个model的话和Provider写法基本一致。

MultiBlocProvider(

providers: [

BlocProvider(

create: (_) => CounterCubit(),

),

BlocProvider(

create: (_) => CounterCubit2(),

),

],

child: BaseBLoCRoute(),

)

 

 

然后在展示数字的widget上开始展示数据了,BlocBuilder中CounterCubit是载体,Model是数据,使用builder回调来刷新UI,刷新UI的条件是buildWhen: (m1, m2) => m1.count != m2.count,当条件满足时进行回调builder.

BlocBuilder(

builder: (_, count) {

print('CounterCubit1 ');

return Row(

mainAxisAlignment: MainAxisAlignment.center,

children: [

Padding(

child: Text(

'count: ${count.count}',

),

padding: EdgeInsets.all(20),

),

OutlineButton(

child: Icon(Icons.arrow_drop_up),

onPressed: () {

context.bloc().addCount(1);

},

),

OutlineButton(

child: Icon(Icons.arrow_drop_down),

onPressed: () {

context.bloc().addCount(-1);

},

)

],

);

},

buildWhen: (m1, m2) => m1.count != m2.count,

)

监听状态变更

/// 监听状态变更

void initState() {

Bloc.observer = SimpleBlocObserver();

super.initState();

}

/// 观察者来观察 事件的变化 可以使用默认的 [BlocObserver]

class SimpleBlocObserver extends BlocObserver {

@override

void onEvent(Bloc bloc, Object event) {

print(event);

super.onEvent(bloc, event);

}

@override

void onChange(Cubit cubit, Change change) {

print(change);

super.onChange(cubit, change);

}

@override

void onTransition(Bloc bloc, Transition transition) {

print(transition);

super.onTransition(bloc, transition);

}

@override

void onError(Cubit cubit, Object error, StackTrace stackTrace) {

print(error);

super.onError(cubit, error, stackTrace);

}

}

 

局部刷新

布局刷新是使用BlocBuilder来实现的,BlocBuilder中CounterCubit是载体,Model是数据,使用builder回调来刷新UI,刷新UI的条件是buildWhen: (m1, m2) => m1.count != m2.count,当条件满足时进行回调builder.本例子是多个model,多个局部UI刷新

Widget _body() {

return Center(

child: CustomScrollView(

slivers: [

SliverToBoxAdapter(

child: BlocBuilder(

builder: (_, count) {

print('CounterCubit1 ');

return Row(

mainAxisAlignment: MainAxisAlignment.center,

children: [

Padding(

child: Text(

'count: ${count.count}',

),

padding: EdgeInsets.all(20),

),

OutlineButton(

child: Icon(Icons.arrow_drop_up),

onPressed: () {

context.bloc().addCount(1);

},

),

OutlineButton(

child: Icon(Icons.arrow_drop_down),

onPressed: () {

context.bloc().addCount(-1);

},

)

],

);

},

buildWhen: (m1, m2) => m1.count != m2.count,

),

),

SliverToBoxAdapter(

child: SizedBox(

height: 50,

),

),

SliverToBoxAdapter(

child: BlocBuilder(

builder: (_, count) {

print('CounterCubit age build ');

return Row(

mainAxisAlignment: MainAxisAlignment.center,

children: [

Padding(

child: Text(

'age:${count.age}',

),

padding: EdgeInsets.all(20),

),

OutlineButton(

child: Icon(Icons.arrow_drop_up),

onPressed: () {

context.bloc().addAge(1);

},

),

OutlineButton(

child: Icon(Icons.arrow_drop_down),

onPressed: () {

context.bloc().addAge(-1);

},

)

],

);

},

buildWhen: (m1, m2) => m1.age != m2.age,

),

),

SliverToBoxAdapter(

child: BlocBuilder(

builder: (_, count) {

print('CounterCubit2 ');

return Column(

children: [

Text('CounterCubit2: ${count.age}'),

OutlineButton(

child: Icon(Icons.add),

onPressed: () {

context.bloc().addAge(1);

},

)

],

);

},

),

)

],

),

);

}

 

 

当我们点击加好或者减号已经被SimpleBlocObserver监听到,看下打印信息,每次model变更都会通知监听者。

flutter: Change { currentState: Model, nextState: Model }

flutter: CounterCubit2

flutter: Change { currentState: Model, nextState: Model }

flutter: CounterCubit2

 

 

复杂状态变更,监听和刷新UI

一个加减例子,每次加减我们在当前组件中监听,当状态变更的时候如何实现刷新UI,而且当age+count == 10的话返回上一页。

要满足此功能的话,同一个部件至少要listener和builder,正好官方提供的BlocConsumer可以实现,如果只需要监听则需要使用BlocListener,简单来说是BlocConsumer=BlocListener+BlocBuilder.

看关键代码:

BlocConsumer(builder: (ctx, state) {

return Column(

children: [

Text(

'age:${context.bloc().state.age} count:${context.bloc().state.count}'),

OutlineButton(

child: Text('age+1'),

onPressed: () {

context.bloc().addAge(1);

},

),

OutlineButton(

child: Text('age-1'),

onPressed: () {

context.bloc().addAge(-1);

},

),

OutlineButton(

child: Text('count+1'),

onPressed: () {

context.bloc().addCount(1);

},

),

OutlineButton(

child: Text('count-1'),

onPressed: () {

context.bloc().addCount(-1);

},

)

],

);

}, listener: (ctx, state) {

if (state.age + state.count == 10) Navigator.maybePop(context);

})

 

 

效果如下:

复杂情况(Cubit)

登陆功能(继承 Cubit)

我们再编写一个完整登陆功能,分别用到BlocListener用来监听是否可以提交数据,用到BlocBuilder用来刷新UI,名字输入框和密码输入框分别用BlocBuilder包裹,实现局部刷新,提交按钮用BlocBuilder包裹用来展示可用和不可用状态。

此为bloc_login的官方例子的简单版本,想要了解更多请查看官方版本

观察者

观察者其实一个APP只需要写一次即可,一般在APP初始化配置即可。我们这里只提供打印状态变更信息。

class DefaultBlocObserver extends BlocObserver {

@override

void onChange(Cubit cubit, Change change) {

if (kDebugMode)

print(

'${cubit.toString()} new:${change.toString()} old:${cubit.state.toString()}');

super.onChange(cubit, change);

}

}

 

 

在初始化指定观察者

@override

void initState() {

Bloc.observer=DefaultBlocObserver();

super.initState();

}

 

 

或者使用默认观察者

Bloc.observer = BlocObserver();

 

State(Model)

存储数据的state(Model),这里我们需要账户信息,密码信息,是否可以点击登录按钮,是否正在登录这些信息。

enum LoginState {

success,

faild,

isLoading,

}

enum BtnState { available, unAvailable }

class LoginModel extends Equatable {

final String name;

final String password;

final LoginState state;

LoginModel({this.name, this.password, this.state});

@override

List get props => [name, password, state, btnVisiable];

LoginModel copyWith({String name, String pwd, LoginState loginState}) {

return LoginModel(

name: name ?? this.name,

password: pwd ?? this.password,

state: loginState ?? this.state);

}

bool get btnVisiable =>

(password?.isNotEmpty ?? false) && (name?.isNotEmpty ?? false);

@override

String toString() {

return '$props';

}

}

 

 

Cubit

装载state的类,当state变更需要调用emit(state),state的变更条件是==,所以我们上边的state(Model)继承了Equatable,Equatable内部实现了操作符==函数,我们只需要将它所需props重写即可。

class LoginCubit extends Cubit {

LoginCubit(state) : super(state);

void login() async {

emit(state.copyWith(loginState: LoginState.isLoading));

await Future.delayed(Duration(seconds: 2));

if (state.btnVisiable == true)

emit(state.copyWith(loginState: LoginState.success));

emit(state.copyWith(loginState: LoginState.faild));

}

void logOut() async {

emit(state.copyWith(

name: null,

pwd: null,

));

}

void changeName({String name}) {

emit(state.copyWith(

name: name, pwd: state.password, loginState: state.state));

}

void changePassword({String pwd}) {

emit(state.copyWith(name: state.name, pwd: pwd, loginState: state.state));

}

}

 

构造view

关键还是得看如何构造UI,首先输入框分别使用BlocBuilder包裹实现局部刷新,局部刷新的关键还是buildWhen得写的漂亮,密码输入框的话只需要判断密码是否改变即可,账号的话只需要判断账号是否发生改变即可,按钮也是如此,在UI外层使用listener来监听状态变更,取所需要的状态跳转新的页面或者弹窗。

首先看下输入框关键代码:

class TextFiledNameRoute extends StatelessWidget {

@override

Widget build(BuildContext context) {

return BlocBuilder(

builder: (BuildContext context, LoginModel state) {

return TextField(

onChanged: (v) {

context.bloc().changeName(name: v);

},

decoration: InputDecoration(

labelText: 'name',

errorText: state.name?.isEmpty ?? false ? 'name不可用' : null),

);

},

buildWhen: (previos, current) => previos.name != current.name);

}

}

class TextFiledPasswordRoute extends StatelessWidget {

@override

Widget build(BuildContext context) {

return BlocBuilder(

builder: (BuildContext context, LoginModel state) {

return TextField(

onChanged: (v) {

context.bloc().changePassword(pwd: v);

},

decoration: InputDecoration(

labelText: 'password',

errorText:

state.password?.isEmpty ?? false ? 'password不可用' : null),

);

},

buildWhen: (previos, current) => previos.password != current.password);

}

}

 

 

按钮根据不同的状态来显示可用或不可用或正在提交的动画效果。

class LoginButton extends StatelessWidget {

@override

Widget build(BuildContext context) {

return BlocBuilder(

builder: (BuildContext context, LoginModel state) {

switch (state.state) {

case LoginState.isLoading:

return const CircularProgressIndicator();

default:

return RaisedButton(

child: const Text('login'),

onPressed: state.btnVisiable

? () {

context.bloc().login();

}

: null,

);

}

},

buildWhen: (previos, current) =>

previos.btnVisiable != current.btnVisiable ||

(current.state != previos.state));

}

}

 

 

小部件写好了,那么我们将他们组合起来

class BaseLoginPageRoute extends StatelessWidget {

@override

Widget build(BuildContext context) {

return BlocProvider(

create: (_) => LoginCubit(LoginModel()),

child: BaseLoginPage(),

);

}

static String routeName = '/BaseLoginPageRoute';

MaterialPageRoute get route =>

MaterialPageRoute(builder: (_) => BaseLoginPageRoute());

}

class BaseLoginPage extends StatefulWidget {

BaseLoginPage({Key key}) : super(key: key);

@override

_BaseLoginPageState createState() => _BaseLoginPageState();

}

class _BaseLoginPageState extends State {

@override

Widget build(BuildContext context) {

return Scaffold(

appBar: AppBar(

title: Text('loginBLoC Cubit'),

),

body: _body(),

);

}

Widget _body() {

return BlocListener(

listener: (context, state) {

if (state.state == LoginState.success) {

Scaffold.of(context)

..hideCurrentSnackBar()

..showSnackBar(const SnackBar(content: Text('登陆成功')));

}

},

child: Center(

child: Column(

children: [

TextFiledNameRoute(),

TextFiledPasswordRoute(),

const SizedBox(

height: 20,

),

LoginButton()

],

),

),

);

}

@override

void initState() {

Bloc.observer = BlocObserver();

super.initState();

}

}

 

 

这里我们实现了登陆成功弹出snackBar.

看下效果图哦:

复杂情况(Bloc)

情况1都我们手动emit(state),那么有没有使用流技术来直接监听的呢?答案是有,那么我们再实现一遍使用bloc的登陆功能。

state(数据载体)

首先我们使用 一个抽象类来定义事件,然后各种小的事件都继承它,比如:NameEvent装载了姓名信息,PasswordEvent装载了密码信息,SubmittedEvent装载了提交信息,简单来讲,event就是每一个按钮点击事件或者valueChange事件触发的动作,最好下载代码之后自己对比下,然后自己从简单例子写,此为稍微复杂情况,看下关键代码:

/// 登陆相关的事件

abstract class LoginEvent extends Equatable {

const LoginEvent();

@override

List get props => [];

}

/// 修改密码

class LoginChagnePassword extends LoginEvent {

final String password;

const LoginChagnePassword({this.password});

@override

List get props => [password];

}

/// 修改账户

class LoginChagneName extends LoginEvent {

final String name;

const LoginChagneName({this.name});

@override

List get props => [name];

}

/// 提交事件

class LoginSubmitted extends LoginEvent {

const LoginSubmitted();

@override

List get props => [];

}

 

 

存储数据的state,在LoginBloc中将event转换成state,那么state需要存储什么数据呢?需要存储账户信息、密码、登陆状态等信息。

/// 事件变更状态[正在请求,报错,登陆成功,初始化]

enum Login2Progress { isRequesting, error, success, init }

/// 存储数据的model 在[bloc]中称作[state]

class LoginState2 extends Equatable {

final String name;

final String password;

final Login2Progress progress;

LoginState2({this.name, this.password, this.progress = Login2Progress.init});

@override

List get props => [name, password, btnVisiable, progress];

LoginState2 copyWith(

{String name, String pwd, Login2Progress login2progress}) {

return LoginState2(

name: name ?? this.name,

password: pwd ?? this.password,

progress: login2progress ?? this.progress);

}

/// 使用 [UserName] &&[UserPassword]来校验规则

bool get btnVisiable => nameVisiable && passwordVisiable;

bool get nameVisiable => UserName(name).visiable;

bool get passwordVisiable => UserPassword(password).visiable;

/// 是否展示名字错误信息

bool get showNameErrorText {

if (name?.isEmpty ?? true) return false;

return nameVisiable == false;

}

/// 是否展示密码错误信息

bool get showPasswordErrorText {

if (password?.isEmpty ?? true) return false;

return passwordVisiable == false;

}

@override

String toString() {

return '$props';

}

}

 

 

event和state写好了,怎么将event转换成state呢?首先新建一个类继承Bloc,覆盖函数mapEventToState,利用这个函数参数event来对state,进行转换,中间因为用到了虚拟的网络登陆,耗时操作和状态变更,所以使用了yield*返回了另外一个流函数。

class LoginBloc extends Bloc {

LoginBloc(initialState) : super(initialState);

@override

Stream mapEventToState(event) async* {

if (event is LoginChagneName) {

yield _mapChangeUserNameToState(event, state);

} else if (event is LoginChagnePassword) {

yield _mapChangePasswordToState(event, state);

} else if (event is LoginSubmitted) {

yield* _mapSubmittedToState(event, state);

}

}

/// 改变密码

LoginState2 _mapChangePasswordToState(

LoginChagnePassword event, LoginState2 state2) {

return state2.copyWith(pwd: event.password ?? '');

}

/// 改变名字

LoginState2 _mapChangeUserNameToState(

LoginChagneName event, LoginState2 state2) {

return state2.copyWith(name: event.name ?? '');

}

/// 提交

Stream _mapSubmittedToState(

LoginSubmitted event, LoginState2 state2) async* {

try {

if (state2.name.isNotEmpty && state2.password.isNotEmpty) {

yield state2.copyWith(login2progress: Login2Progress.isRequesting);

await Future.delayed(Duration(seconds: 2));

yield state2.copyWith(login2progress: Login2Progress.success);

yield state2.copyWith(login2progress: Login2Progress.init);

}

} on Exception catch (e) {

yield state2.copyWith(login2progress: Login2Progress.error);

}

}

}

 

 

state和event事件整理成图方便理解一下:

构造view

样式我们还是使用上边的 ,但是发送事件却不一样,原因是继承bloc其实是实现了EventSink的接口,使用add()触发监听。

class TextFiledNameRoute extends StatelessWidget {

@override

Widget build(BuildContext context) {

return BlocBuilder(

builder: (BuildContext context, LoginState2 state) {

return TextField(

onChanged: (v) {

context.bloc().add(LoginChagneName(name: v));

},

textAlign: TextAlign.center,

decoration: InputDecoration(

labelText: 'name',

errorText:

(state.showNameErrorText == true) ? 'name不可用' : null),

);

},

buildWhen: (previos, current) => previos.name != current.name);

}

}

  

 

完整的效果是:

BLoC 流程

首先view部件持有Cubit,Cubit持有状态(Model),当状态(Model)发生变更时通知Cubit,Cubit依次通知listener、BlocBulder.builder进行刷新UI,每次状态变更都会通知BlocObserver,可以做到全局的状态监听。

千言万语不如一张图:

参考

BLoC官方

文章例子code 仓库

just for 10k now

do it

just do it

I believe I can I do ....

posted @

2020-08-20 14:02 

ifgyong 

阅读(1566) 

评论(0) 

编辑 

收藏 

举报

会员力量,点亮园子希望

刷新页面返回顶部

公告

Copyright © 2024 ifgyong

Powered by .NET 8.0 on Kubernetes

如何使用BLoC架构开发Flutter应用_大前端_Svetlana Cherednichenko_InfoQ精选文章

如何使用BLoC架构开发Flutter应用_大前端_Svetlana Cherednichenko_InfoQ精选文章

Flutter 状态管理 Bloc 使用 - 简书

ter 状态管理 Bloc 使用 - 简书登录注册写文章首页下载APP会员IT技术Flutter 状态管理 Bloc 使用_凌浩雨关注赞赏支持Flutter 状态管理 Bloc 使用Bloc官网

Bloc Github

vs code Bloc插件

Flutter Bloc Pub地址

依赖

dependencies:

bloc: ^2.0.0

flutter_bloc: ^2.1.1

下载包

flutter packages get

导入

import 'package:bloc/bloc.dart';

import 'package:flutter_bloc/flutter_bloc.dart';

Bloc 组件

BlocBuilder

BlocBuilder是Flutter窗口小部件,需要Bloc和builder函数。BlocBuilder处理构建小部件以响应新状态。如果省略了bloc参数,BlocBuilder将使用BlocProvider和当前函数自动执行查找BuildContext。如果你想再构造器函数被调用时,自己控制页面的刷新,那么你可以提供一个更细粒度的condition来空中BlocBuilder。在condition中有之前的状态和当前的状态两个参数,方法返回一个布尔值。如果返回true, builder将被调用,且widget会重新绘制; 返回false, builder则不会重新绘制。

BlocBuilder(

bloc: blocA, // 提供一个本地Bloc实例

condition: (previousState, state) {

// 返回true/false决定是否重建组件状态

},

builder: (context, state) {

// 返回组件

}

)

BlocProvider

BlocProvider 是 通过BlocProvider.of(context)为其子元素提供Bloc 的Flutter widget。它使用依赖注入便于单例Bloc提供给子树的多个小部件。在大多数情况下,BlocProvider应使用它来创建新的属性bloc,并将其提供给其余的子树。在这种情况下,由于BlocProvider负责创建Bloc,它将自动处理关闭块。

BlocProvider(

create: (BuildContext context) => BlocA(),

child: ChildA(),

);

在一些情况下,BlocProvider可用于向子元素提供现有的Bloc。在这种情况下,BlocProvider由于不会创建新的Bloc,因此不会自动关闭Bloc。

BlocProvider.value(

value: BlocProvider.of(context),

child: ScreenA(),

);

在ChildA或ScreenA中使用BlocProvider.of(context)获取BlocA对象

MultiBlocProvider

MultiBlocProvider是可将多个BlocProvider小部件合并为一个BlocProvider的Flutter widget。 MultiBlocProvider提高了可读性,消除了嵌套多个元素的需求BlocProvider。

MultiBlocProvider(

providers: [

BlocProvider(

create: (BuildContext context) => BlocA(),

),

BlocProvider(

create: (BuildContext context) => BlocB(),

),

BlocProvider(

create: (BuildContext context) => BlocC(),

),

],

child: ChildA(),

)

BlocListener

BlocListener 包含一个 BlocWidgetListener 和可选的Bloc, 并且listener每在状态改变时被调用。

BlocListener(

bloc: blocA,

listener: (context, state) {

// 基于BlocA的状态做一些处理

},

child: Container()

)

如果希望更加细粒度的控制listener被调用, 那么可以实现condition来控制, 它将会返回一个布尔值, 如果返回true, listener将会被调用; 如果返回false, listener将不会被调用.

BlocListener(

condition: (previousState, state) {

// 返回 true/false 决定是否调用监听

},

listener: (context, state) {

// 基于BlocA的状态做一些处理

}

child: Container(),

)

MultiBlocListener

MultiBlocListener 可将多个BlocListener小部件合并为一个BlocListener。 MultiBlocListener提高了可读性,消除了嵌套多个元素的需求BlocListeners。

MultiBlocListener(

listeners: [

BlocListener(

listener: (context, state) {},

),

BlocListener(

listener: (context, state) {},

),

BlocListener(

listener: (context, state) {},

),

],

child: ChildA(),

)

RepositoryProvider

RepositoryProvider 通过RepositoryProvider.of(context)为其子项提供存储库。它使用依赖注入将存储库的单个实例提供给子树中的多个小部件。BlocProvider应该用于提供Bloc,而RepositoryProvider只能用于存储库。

RepositoryProvider(

builder: (context) => RepositoryA(),

child: ChildA(),

);

在ChildA中使用RepositoryProvider.of(context) 获取RepositoryA.

MultiRepositoryProvider

MultiRepositoryProvider 将多个RepositoryProvider小部件合并为一个RepositoryProvider。 MultiRepositoryProvider提高了可读性,消除了嵌套多个元素的需求RepositoryProvider。

MultiRepositoryProvider(

providers: [

RepositoryProvider(

builder: (context) => RepositoryA(),

),

RepositoryProvider(

builder: (context) => RepositoryB(),

),

RepositoryProvider(

builder: (context) => RepositoryC(),

),

],

child: ChildA(),

)

使用

事件枚举

/// 计数事件

enum CounterEvent {

increment,

decrement

}

计数Bloc

import 'package:bloc/bloc.dart';

import 'event.dart';

/// Bloc 计数

class CounterBloc extends Bloc {

@override

int get initialState => 0;

@override

Stream mapEventToState(event) async* {

switch (event) {

case CounterEvent.increment:

yield state + 1;

break;

case CounterEvent.decrement:

yield state - 1;

break;

default:

throw Exception("未知Event");

}

}

}

测试页面

import 'package:flutter/material.dart';

import 'package:flutter_bloc/flutter_bloc.dart';

import 'package:state_manage/counter/counter_bloc.dart';

import 'package:state_manage/counter/event.dart';

/// 测试页面

class CounterTest extends StatelessWidget {

@override

Widget build(BuildContext context) {

return MaterialApp(

title: 'Counter',

theme: ThemeData(

primarySwatch: Colors.blue,

),

home: BlocProvider(

// 使用 BlocProvider

create: (context) => CounterBloc(),

child: MyHomePage(title: 'Counter'),

));

}

}

class MyHomePage extends StatefulWidget {

MyHomePage({Key key, this.title}) : super(key: key);

final String title;

@override

_MyHomePageState createState() => _MyHomePageState();

}

class _MyHomePageState extends State {

@override

Widget build(BuildContext context) {

// 获取Bloc

final CounterBloc bloc = BlocProvider.of(context);

return Scaffold(

appBar: AppBar(

title: Text(widget.title),

),

body: BlocBuilder(

builder: (context, count) {

return Center(

child: Column(

mainAxisAlignment: MainAxisAlignment.center,

children: [

Text(

'You have pushed the button this many times:',

),

Text(

'$count',

style: Theme.of(context).textTheme.display1,

),

],

),

);

},

),

floatingActionButton: Column(

crossAxisAlignment: CrossAxisAlignment.end,

mainAxisAlignment: MainAxisAlignment.end,

children: [

// 加

Padding(

padding: EdgeInsets.symmetric(vertical: 5.0),

child: FloatingActionButton(

onPressed: () {

bloc.add(CounterEvent.increment);

},

tooltip: 'Increment',

child: Icon(Icons.add),

),

),

// 减

Padding(padding: EdgeInsets.symmetric(vertical: 5.0),

child: FloatingActionButton(

onPressed: () {

bloc.add(CounterEvent.decrement);

},

tooltip: 'Decrement',

child: Icon(Icons.add),

),)

],

));

}

}

主函数

// 状态管理之计数器

void main() => runApp(CounterTest());

效果图.gif

最后编辑于 :2019.12.11 16:26:31©著作权归作者所有,转载或内容合作请联系作者人面猴序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...沈念sama阅读 145,261评论 1赞 308死咒序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...沈念sama阅读 62,177评论 1赞 259救了他两次的神仙让他今天三更去死文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...开封第一讲书人阅读 96,329评论 0赞 214道士缉凶录:失踪的卖姜人 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...开封第一讲书人阅读 41,490评论 0赞 184港岛之恋(遗憾婚礼)正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...茶点故事阅读 49,353评论 1赞 262恶毒庶女顶嫁案:这布局不是一般人想出来的文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...开封第一讲书人阅读 39,028评论 1赞 179城市分裂传说那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...沈念sama阅读 30,611评论 2赞 276双鸳鸯连环套:你想象不到人心有多黑文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...开封第一讲书人阅读 29,383评论 0赞 171万荣杀人案实录序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...沈念sama阅读 32,749评论 0赞 215护林员之死正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...茶点故事阅读 29,460评论 2赞 219白月光启示录正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...茶点故事阅读 30,814评论 1赞 232活死人序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...沈念sama阅读 27,255评论 2赞 215日本核电站爆炸内幕正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...茶点故事阅读 31,752评论 3赞 214男人毒药:我在死后第九天来索命文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...开封第一讲书人阅读 25,685评论 0赞 9一桩弑父案,背后竟有这般阴谋文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...开封第一讲书人阅读 26,114评论 0赞 170情欲美人皮我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...沈念sama阅读 33,747评论 2赞 234代替公主和亲正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...茶点故事阅读 33,901评论 2赞 238评论4赞33赞4赞赞赏更