Flutter状态管理插件bloc_builder的使用
Flutter状态管理插件bloc_builder的使用
bloc_builder
bloc_builder
是一个用于创建 BLoC(业务逻辑组件)的简单方法。您可以在这里查看完整的文档:bloc_builder 文档。
bloc_builder 2 将不再支持 Stream,请使用 flutter_live_data 或 LiveData.stream()
Helper Endpoint
$watch
标准小部件,每次 LiveData 更新值时都会重新渲染。
(如果 LiveData 设置了标志 verifyDataChange: true
,则只有在值发生变化时才会重新渲染)
(verifyDataChange
的默认值为 false
)
LiveData<int> counter = LiveData(0, verifyDataChange: false);
Widget build(BuildContext context) {
return $watch(counter, build: (context, int count){
return Text('counter is $count');
});
}
$watchMany
在需要使用多个 LiveData 的情况下:
LiveData<int> $a = LiveData(1);
LiveData<String?> $b = LiveData<String?>(null);
LiveData<bool> $c = LiveData(false);
如果对每个 LiveData 使用 $watch
会导致嵌套的 $watch
:
$watch($a, (context, int a){
return $watch($b, (context, String? b){
return $watch($a, (context, bool c){
return Text('x=$x, y=$y, z=$z');
})
})
})
对于这种情况,有一个 makeMemorize
函数可以将 LiveData 分组,您可以设置每个 LiveData 的键。
$watch(
makeMemorize({
#a: logic.$a,
#b: logic.$b,
#c: logic.$c,
}).owner(...), build: (context, Memorize m) {
int x = m[#a] as int;
String? y = m[#b] as String?;
bool z = m[#c] as bool;
return Text('x=$x, y=$y, z=$z');
})
// 或者
$watchMany({
#a: logic.$a,
#b: logic.$b,
#c: logic.$c,
}, owner: ..., build: (context, Memorize m) {
int x = m[#a] as int;
String? y = m[#b] as String?;
bool z = m[#c] as bool;
return Text('x=$x, y=$y, z=$z');
})
$when
当您希望基于条件进行渲染时使用。第一个匹配的 $case
将被渲染,否则会执行 $else
。
LiveData<int> counter = LiveData(0);
Widget build(BuildContext context) {
return $when(counter) |
$case(
(int value) => value % 2 == 0,
build: (context, int count){
return Text('$count is Even');
},
) |
$case(
(int value) => value % 2 != 0,
build: (context, int count){
return Text('$count is Odd');
},
) |
$else(
build: (context, int count){
return Text('Impossible!');
},
),;
}
但是在某些情况下,Dart 泛型类型不适用于 $case
。因此,我们建议使用以下最佳实践模式:
LiveData<int> counter = LiveData(0);
Widget build(BuildContext context) {
return $when(counter)
..$case(
(int value) => value % 2 == 0,
build: (context, int count){
return Text('$count is Even');
},
)
..$case(
(int value) => value % 2 != 0,
build: (context, int count){
return Text('$count is Odd');
},
)
..$else(
build: (context, int count){
return Text('Impossible!');
},
),
}
还有减少 $when
代码的快捷方式:if
, else
, true
, false
$if(
counter,
(int value) => value % 2 == 0,
build: (context, int count){
return Text('$count is Even');
},
) |
$else(
build: (context, int count){
return Text('$count is Odd');
},
)
$when(isEven) |
$true(build: (context, int count){
return Text('$count is Even');
}) |
$false(build: (context, int count){
return Text('$count is Odd');
}),
$guard
类似于 $when
,但 $guard
允许您根据不同的 LiveData 进行条件判断,并在条件匹配时终止。
例如:我们想要显示计数器数字,但是有加载状态和错误状态(如果加载失败等)。
我们可以正常地为计数器创建 $watch
,并将加载状态和错误状态的 UI 分离到 $guard
中:
LiveData<int> counter = LiveData(0);
LiveData<bool> loading = LiveData(false);
LiveData<String?> errorMessage = LiveData(null);
Widget build(BuildContext context) {
return $guard(
loading,
when: (loading) => loading == true,
build: (context, isLoading){
return Text('now loading...');
},
) |
$guard.isNotNull(
errorMessage,
build: (context, msg){
return Text('error: $msg');
},
) |
$watch(counter, build: (context, int count){
return Text('counter is $count');
}),
}
注意:从这个例子中,你会看到 $guard
需要添加 when
条件,而 $guard.isNotNull
则不需要 when
条件。
isNotNull
是 Guard 条件辅助函数。
Guard Helper
$guard.isNull
$guard.isNotNull
$guard.isEmpty // 适用于 String 和 List
$guard.isNotEmpty // 适用于 String 和 List
$guard.isTrue
$guard.isFalse
$for
用于创建 ListView(或 AnimatedList 等),它只接收包含列表的 LiveData。
LiveData<List<String>> items = LiveData(<String>["A", "B", "C"]);
Widget build(BuildContext context) {
return $for(items);
}
$for
有选项自定义列表和项目:
buildItem
: 用于构建每个项目的 Widget(默认是toString
文本)buildList
: 用于构建 ListView,您将接收到由buildItem
构建的小部件作为参数(默认是ListView.builder
)buildEmpty
: 数据为空的情况(默认是无小部件)
LiveData<List<String>> items = LiveData(<String>["A", "B", "C"]);
Widget build(BuildContext context) {
return $for(
items,
buildItem: (context, String item, int index){
return Text('$item');
},
buildList: (context, List<ItemViewHolder<T>> holder){
// ItemViewHolder 有两个属性
// - T data
// - Widget widget
return ListView.builder(
itemCount: holder.length,
itemBuilder: (context, int i) => holder[i].widget,
);
},
buildEmpty: (context, List<String> data){
return Text('empty!');
},
);
}
$for 与嵌套 LiveData
在某些情况下,我们需要直接修改每个项目的数据。
class Item {
int id;
String name;
...
}
LiveData<List<Item>> $items = LiveData(<Item>[...]);
$for($items);
$items.value[0] = ...;
$items.tick();
在这个例子中,我们想修改索引为 0 的元素。
是的,您可以修改对象的状态,但根据 LiveData 的概念,您必须调用 tick
来通知 LiveData 发生了更改。
这是问题所在! 当我们调用 tick
时,这将触发包含列表的 LiveData,这意味着即使我们只修改列表中的一个对象,它也需要重新渲染整个列表。
直接的解决方案是您必须将 LiveData 添加到您的模型中(在这种情况下是 Item
)。
但是,当然,您不想修改模型类。因此,还有一个解决方案,即让 LiveData 帮助您处理这个问题,通过使用 eachItemsInListAsLiveData
。
class Item {
int id;
String name;
...
}
LiveData<List<Item>> $items = LiveData(<Item>[...])
.apply(eachItemsInListAsLiveData());
$for($items);
// 更新列表
$items.value = ...;
// 更新每个元素
detach($items, $items.value[0])!.value = ...
当使用 eachItemsInListAsLiveData
时,您可以通过 detach
从父 LiveData 中获取每个项目的 LiveData。
背后的原理
eachItemsInListAsLiveData
将调用 attach
,这将基于您提供的项创建 LiveData 并将其绑定到父 LiveData。
LiveData<List<Item>> $items = LiveData(<Item>[...]);
attach($items, $items.value[0]);
attach($items, $items.value[1]);
attach($items, $items.value[2]);
...
detach($items, $items.value[0])!.value = ...
detach($items, $items.value[1])!.value = ...
detach($items, $items.value[2])!.value = ...
此外,
attach($items, $items.value[0])
这表示我们希望 $items.value[0]
成为 LiveData 并绑定到 $items
。
建议您将父 LiveData 作为自己的列表使用,因为新的 LiveData 可以遵循其父 LiveData 的生命周期。
最后,与 attach
相反的是 detach
,当您想要获取 LiveData 回来时使用。
detach($items, $items.value[2])
更多关于Flutter状态管理插件bloc_builder的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter状态管理插件bloc_builder的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
BlocBuilder
是 flutter_bloc
包中的一个 widget,用于根据 BLoC
的状态来构建 UI。它会在 BLoC
的状态发生变化时自动重建,从而更新 UI。BlocBuilder
是 Flutter 中实现状态管理的一种有效方式。
安装 flutter_bloc
首先,你需要在 pubspec.yaml
文件中添加 flutter_bloc
依赖:
dependencies:
flutter:
sdk: flutter
flutter_bloc: ^8.0.0
然后运行 flutter pub get
来安装依赖。
使用 BlocBuilder
1. 定义 BLoC 和事件
首先,你需要定义一个 BLoC
和一些事件。假设我们有一个简单的计数器应用:
import 'package:flutter_bloc/flutter_bloc.dart';
// 定义事件
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}
// 定义 BLoC
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0);
[@override](/user/override)
Stream<int> mapEventToState(CounterEvent event) async* {
if (event is IncrementEvent) {
yield state + 1;
} else if (event is DecrementEvent) {
yield state - 1;
}
}
}
2. 在 UI 中使用 BlocBuilder
接下来,你可以在 UI 中使用 BlocBuilder
来响应 BLoC
的状态变化:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
home: BlocProvider(
create: (context) => CounterBloc(),
child: CounterPage(),
),
);
}
}
class CounterPage extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Counter App')),
body: Center(
child: BlocBuilder<CounterBloc, int>(
builder: (context, state) {
return Text(
'Count: $state',
style: TextStyle(fontSize: 24),
);
},
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () {
context.read<CounterBloc>().add(IncrementEvent());
},
child: Icon(Icons.add),
),
SizedBox(height: 10),
FloatingActionButton(
onPressed: () {
context.read<CounterBloc>().add(DecrementEvent());
},
child: Icon(Icons.remove),
),
],
),
);
}
}