sentry_flutter

Sentry是一款多平台支持的异常信息收集解决方案。sentry_flutter可以在Flutter项目中收集异常信息。

https://docs.sentry.io/platforms/flutter/可以查阅其Flutter项目的集成文档。

sentry_dart_plugin可以自动上传mapping.txt文件和debug的动态库,方便后续直接在sentry后台查看异常信息。

基本用法

  1. pubspec.yaml配置,sentry_dart_plugin配置参数参考https://github.com/getsentry/sentry-dart-plugin/blob/main/README.md
Read More

单元测试

单元测试的作用?

  • 保证代码质量,当一个方法在某个版本进行了调整,如果对应的单元测试无法通过,说明这个方法的改动有问题。单元测试中也能包含很多边界条件,甚至可以把测试用例的场景都包含,这样就相当于做了一遍测试的工作,而且可以复用检查,下次迭代时再运行一遍,说不定能发现新问题
  • 单元测试阶段发现bug的处理时间相对后期测试发现反馈给研发,处理的时间要短很多,提高效率

Read More

form_bloc

form_bloc是结合bloc的表单库,运用bloc的特性实现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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
import 'package:flutter/material.dart';
import 'package:flutter_form_bloc/flutter_form_bloc.dart';

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

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

@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: LoginForm(),
);
}
}

class LoginFormBloc extends FormBloc<String, String> {
// 不同的表单项对应不同的FieldBloc
final email = TextFieldBloc(
validators: [
FieldBlocValidators.required,
FieldBlocValidators.email,
],
);

final password = TextFieldBloc(
validators: [
FieldBlocValidators.required,
],
);

final showSuccessResponse = BooleanFieldBloc();

LoginFormBloc() {
//添加表单项
addFieldBlocs(
fieldBlocs: [
email,
password,
showSuccessResponse,
],
);
}

/// 表单提交方法
@override
void onSubmitting() async {
debugPrint(email.value);
debugPrint(password.value);
debugPrint(showSuccessResponse.value.toString());

await Future<void>.delayed(const Duration(seconds: 1));

// 提交进度变化
emitSubmitting(progress: 0.2);
await Future<void>.delayed(Duration(milliseconds: 400));
emitSubmitting(progress: 0.6);
await Future<void>.delayed(Duration(milliseconds: 400));
emitSubmitting(progress: 1.0);
//取消提交
//emitSubmissionCancelled();

if (showSuccessResponse.value) {
emitSuccess(); //提交成功,发送成功消息,触发UI更新
} else {
emitFailure(failureResponse: 'This is an awesome error!'); //提交失败,发送失败消息,触发UI更新
}
}

@override
Future<void> close() {
// 释放所有表单项
email.close();
password.close();
showSuccessResponse.close();
return super.close();
}

}

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

@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => LoginFormBloc(),
child: Builder(
builder: (context) {
final loginFormBloc = context.read<LoginFormBloc>();

return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(title: const Text('Login')),
body: FormBlocListener<LoginFormBloc, String, String>(
onSubmitting: (context, state) {
LoadingDialog.show(context);
},
onSubmissionFailed: (context, state) {
LoadingDialog.hide(context);
},
onSuccess: (context, state) {
LoadingDialog.hide(context);

Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const SuccessScreen()));
},
onFailure: (context, state) {
LoadingDialog.hide(context);

ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.failureResponse!)));
},
child: SingleChildScrollView(
physics: const ClampingScrollPhysics(),
child: AutofillGroup(
child: Column(
children: <Widget>[
TextFieldBlocBuilder(
textFieldBloc: loginFormBloc.email,
keyboardType: TextInputType.emailAddress,
autofillHints: const [
AutofillHints.username,
],
decoration: const InputDecoration(
labelText: 'Email',
prefixIcon: Icon(Icons.email),
),
),
TextFieldBlocBuilder(
textFieldBloc: loginFormBloc.password,
suffixButton: SuffixButton.obscureText,
autofillHints: const [AutofillHints.password],
decoration: const InputDecoration(
labelText: 'Password',
prefixIcon: Icon(Icons.lock),
),
),
SizedBox(
width: 250,
child: CheckboxFieldBlocBuilder(
booleanFieldBloc: loginFormBloc.showSuccessResponse,
body: Container(
alignment: Alignment.centerLeft,
child: const Text('Show success response'),
),
),
),
ElevatedButton(
onPressed: loginFormBloc.submit,
child: const Text('LOGIN'),
),
],
),
),
),
),
);
},
),
);
}
}

class LoadingDialog extends StatelessWidget {
static void show(BuildContext context, {Key? key}) => showDialog<void>(
context: context,
useRootNavigator: false,
barrierDismissible: false,
builder: (_) => LoadingDialog(key: key),
).then((_) => FocusScope.of(context).requestFocus(FocusNode()));

static void hide(BuildContext context) => Navigator.pop(context);

const LoadingDialog({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: Center(
child: Card(
child: Container(
width: 80,
height: 80,
padding: const EdgeInsets.all(12.0),
child: const CircularProgressIndicator(),
),
),
),
);
}
}

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

@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Icon(Icons.tag_faces, size: 100),
const SizedBox(height: 10),
const Text(
'Success',
style: TextStyle(fontSize: 54, color: Colors.black),
textAlign: TextAlign.center,
),
const SizedBox(height: 10),
ElevatedButton.icon(
onPressed: () => Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const LoginForm())),
icon: const Icon(Icons.replay),
label: const Text('AGAIN'),
),
],
),
),
);
}
}


表单项类型

Read More