Bloc 践行

Cubit简单使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import 'package:bloc/bloc.dart';

class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);

void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}

void main() async {
final cubit = CounterCubit();

final streamSubscription = cubit.stream.listen(print);

cubit.increment();

cubit.increment();

cubit.decrement();

await Future.delayed(Duration.zero);
await streamSubscription.cancel();
await cubit.close();
}

Bloc 使用枚举事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import 'package:bloc/bloc.dart';

enum CounterEvent { increment, decrement }

class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on((event, emit) {
if (event == CounterEvent.increment) {
return emit(state + 1);
}
return emit(state - 1);
});
}
}

Future<void> main() async {
final bloc = CounterBloc();
final streamSubscription = bloc.stream.listen(print);

bloc.add(CounterEvent.increment);
bloc.add(CounterEvent.increment);
bloc.add(CounterEvent.decrement);

await Future.delayed(Duration.zero);
await streamSubscription.cancel();
await bloc.close();
}

BlocProvider & BlocBuilder

counter_cubit.dart

1
2
3
4
5
6
7
8
9
10
import 'package:bloc/bloc.dart';

part 'counter_state.dart';

class CounterCubit extends Cubit<CounterState> {
CounterCubit() : super(CounterState(counterValue: 0));

void increment() => emit(CounterState(counterValue: state.counterValue + 1));
void decrement() => emit(CounterState(counterValue: state.counterValue - 1));
}

counter_state.dart

1
2
3
4
5
6
part of 'counter_cubit.dart';

class CounterState {
int counterValue;
CounterState({required this.counterValue});
}

UI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import 'package:bloc_counter/cubit/counter_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return BlocProvider<CounterCubit>(
create: (context) => CounterCubit(),
child: MaterialApp(
title: 'My App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: BodyWidget(),
),
);
}
}

class BodyWidget extends StatelessWidget {
const BodyWidget({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('My App')),
body: Center(
child: BlocBuilder<CounterCubit, CounterState>(
builder: (context, state) {
return Text(
'${state.counterValue}',
style: Theme.of(context).textTheme.headline2,
);
},
),
),
floatingActionButton: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton.small(
onPressed: () {
// BlocProvider.of<CounterCubit>(context).increment();
context.read<CounterCubit>().increment();
},
child: const Icon(Icons.plus_one),
),
FloatingActionButton.small(
onPressed: () {
// BlocProvider.of<CounterCubit>(context).decrement();
context.read<CounterCubit>().decrement();
},
child: const Icon(Icons.exposure_minus_1),
),
],
),
);
}
}

注意:在定义BlockProvider组件内部使用Bloc需要在子组件外部包裹一个builder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@override
Widget build(BuildContext context){
BlocProvider(
create: (_)=>BlocA(),
child: Builder(){
builder: (context) => RaisedButton(
onPressed:(){
final blocA = BlocProvider.of<BlocA>(context);
},

)
}
)
}

BlocListener

BlocBuilder 可能执行多次,但BlocListener只在状态改变时执行一次(不包含初始值)。

一般在BlocListener中做一些辅助动作,比如弹出消息。

1
2
3
4
5
6
7
8
9
10
import 'package:bloc/bloc.dart';

part 'counter_state.dart';

class CounterCubit extends Cubit<CounterState> {
CounterCubit() : super(CounterState(counterValue: 0));

void increment() => emit(CounterState(counterValue: state.counterValue + 1, isIncremented: true));
void decrement() => emit(CounterState(counterValue: state.counterValue - 1, isIncremented: false));
}
1
2
3
4
5
6
7
part of 'counter_cubit.dart';

class CounterState {
int counterValue;
bool? isIncremented;
CounterState({required this.counterValue, this.isIncremented});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import 'package:bloc_counter/cubit/counter_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return BlocProvider<CounterCubit>(
create: (context) => CounterCubit(),
child: MaterialApp(
title: 'My App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: BodyWidget(),
),
);
}
}

class BodyWidget extends StatelessWidget {
const BodyWidget({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
print('BodyWidget build');
return Scaffold(
appBar: AppBar(title: const Text('My App')),
body: BlocListener<CounterCubit, CounterState>(
listener: (context, state) {
print('BlocListener');
if (state.isIncremented == true) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Increment'),
duration: Duration(milliseconds: 300),
),
);
} else if (state.isIncremented == false) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Decrement'),
duration: Duration(milliseconds: 300),
),
);
}
},
child: Center(
child: BlocBuilder<CounterCubit, CounterState>(
builder: (context, state) {
print('bloc build text');
return Text(
'${state.counterValue}',
style: Theme.of(context).textTheme.headline2,
);
},
),
),
),
floatingActionButton: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton.small(
onPressed: () {
// BlocProvider.of<CounterCubit>(context).increment();
context.read<CounterCubit>().increment();
},
child: const Icon(Icons.plus_one),
),
FloatingActionButton.small(
onPressed: () {
// BlocProvider.of<CounterCubit>(context).decrement();
context.read<CounterCubit>().decrement();
},
child: const Icon(Icons.exposure_minus_1),
),
],
),
);
}
}

BlocConsumer

BlocConsumer = BlocBuilder + BlocListener

1
2
3
4
BlocConsumer<BlocA, BlocAState>(
listener: (context, state){},
builder: (context, state){}
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import 'package:bloc_counter/cubit/counter_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return BlocProvider<CounterCubit>(
create: (context) => CounterCubit(),
child: MaterialApp(
title: 'My App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: BodyWidget(),
),
);
}
}

class BodyWidget extends StatelessWidget {
const BodyWidget({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
print('BodyWidget build');
return Scaffold(
appBar: AppBar(title: const Text('My App')),
body: BlocConsumer<CounterCubit, CounterState>(
listener: (context, state) {
print('BlocListener');
if (state.isIncremented == true) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Increment'),
duration: Duration(milliseconds: 300),
),
);
} else if (state.isIncremented == false) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Decrement'),
duration: Duration(milliseconds: 300),
),
);
}
},
builder: (BuildContext context, Object? state) {
return Center(
child: BlocBuilder<CounterCubit, CounterState>(
builder: (context, state) {
print('bloc build text');
return Text(
'${state.counterValue}',
style: Theme.of(context).textTheme.headline2,
);
},
),
);
},
),
floatingActionButton: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton.small(
onPressed: () {
// BlocProvider.of<CounterCubit>(context).increment();
context.read<CounterCubit>().increment();
},
child: const Icon(Icons.plus_one),
),
FloatingActionButton.small(
onPressed: () {
// BlocProvider.of<CounterCubit>(context).decrement();
context.read<CounterCubit>().decrement();
},
child: const Icon(Icons.exposure_minus_1),
),
],
),
);
}
}

RepositoryProvider

使用方法同BlocProvider

MuitiBolcProvider

注意:要传入不同名称的context,不知道为啥需要不同的名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
MultiBlocProvider(
providers: [
BlocProvider<InternetCubit>(
create: (internetCubitContext) => InternetCubit(connectivity: connectivity),
),
BlocProvider<CounterCubit>(
create: (counterCubitContext) => CounterCubit(internetCubit: context.read<InternetCubit>()),
)
],
child: MaterialApp(
title: 'My App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: {
'/': (context) => HomeScreen(),
'second': (context) => SecondScreen(),
},
),
);

MultiBlocListener

使用方法同MultiBlocProvider

MultiRepositoryProvider

使用方法同MultiBlocProvider

Bloc 项目目录结构

architecture

Data Layer

Model

相当于java中的实体类,dto

Provider

数据访问类,调用api返回原始数据。

Repository

调用 provider 对原始数据进行再加工,然后把加工后的数据返回给业务逻辑层。

Bloc单元测试

安装依赖:equatable
安装开发依赖:test 和 bloc_test

修改状态类

1
2
3
4
5
6
7
8
9
10
part of 'counter_cubit.dart';

class CounterState extends Equatable {
int counterValue;
bool? isIncremented;
CounterState({required this.counterValue, this.isIncremented});

@override
List<Object?> get props => [this.counterValue, this.isIncremented];
}

lib目录同级创建test目录,此目录用于保存测试类

counter_cubit_test.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import 'package:bloc_counter/cubit/counter_cubit.dart';
import 'package:bloc_test/bloc_test.dart';
import 'package:test/test.dart';

void main() {
group('CounterCubit', () {
late CounterCubit counterCubit;

/// 初始化
setUp(() {
counterCubit = CounterCubit();
});

/// 释放资源
tearDown(() {
counterCubit.close();
});

test('CounterCubit 初始值是否为0', () {
expect(counterCubit.state, CounterState(counterValue: 0));
});

blocTest<CounterCubit, CounterState>(
'the cubit should emit a CounterState(counterValue:1, isIncremented: true) when cubit.increment() function is called',
build: () => counterCubit,
act: (cubit) => cubit.increment(),
expect: () => [CounterState(counterValue: 1, isIncremented: true)],
);
});
}

在组件树中查找bloc

context.watch

1
2
3
4
5
6
7
8
Builder(
builder:(context){
final stateA = context.watch<BlocA>().state;
final stateB = context.watch<BlocB>().state;

return MyWidget(stateA,stateB);
}
)

context.select

1
2
3
4
5
6
7
8
9
Builder(
builder: (context){
final name = context.select(
(UserBloc bloc)=>bloc.state.user.naem
);

return Text('$name');
}
)

context.read

1
2
3
4
5
6
7
@override
Widget build(BuildContext context){
final bloc = context.read<MyBloc>();
return RaisedButton(
onPressed:()=>bloc.add(MyEvent()),
);
}