Flutter BLoC 模式的轻量级开发工具包插件potatoes的使用
Flutter BLoC 模式的轻量级开发工具包插件potatoes的使用
1. 插件概述
Potatoes
是一个基于 BLoC 模式的轻量级开发工具包,旨在简化代码中重复逻辑的编写。它提供了一系列类和工具,帮助开发者快速构建应用程序。如果你不熟悉 BLoC 模式,建议先了解 Flutter BLoC。
Potatoes
还提供了一套简单的状态管理逻辑,帮助你有效地循环处理 Cubit 的状态。
2. Cubit 状态管理
在 Potatoes
中,Cubit 状态是状态机中的步骤,可以在不同的状态之间交替。每个状态可以是永久的(提供用户界面)或临时的(仅用于通知监听器)。你可以通过扩展相应的类来为状态分配角色。
以下是几种常见的状态类型:
- CubitSuccessState:用于空闲或永久成功状态。
- CubitLoadingState:用于加载步骤,显示加载屏幕并防止用户重复触发请求。
- CubitInformationState:用于一次性事件,例如显示对话框或触发一次性的动作。
- CubitErrorState:用于跟踪序列中的错误,并提供错误原因和堆栈跟踪。
示例:登录 Cubit
// 定义状态基类
mixin LoginState on Equatable {}
// 空闲状态
class LoginIdleState extends CubitSuccessState with LoginState {
final String? email;
final String? password;
const LoginIdleState.empty() : email = null, password = null;
const LoginIdleState(this.email, this.password);
[@override](/user/override)
List<Object?> get props => [email, password];
}
// 加载状态
class LoggingInState extends CubitLoadingState with LoginState {
const LoggingInState();
}
// 需要 OTP 的状态
class LoginNeedsOTPState extends CubitInformationState with LoginState {
const LoginNeedsOTPState();
}
// 错误状态
class LoginErrorState extends CubitErrorState with LoginState {
LoginErrorState(super.error, [super.trace]);
}
// 登录 Cubit
class LoginCubit extends Cubit<LoginState> {
final AuthService authService;
LoginCubit(this.authService) : super(const LoginIdleState.empty());
void login() {
// 只有在空闲状态下才执行登录操作
if (state is LoginIdleState) {
final stateBefore = state as LoginIdleState;
// 显示加载状态
emit(const LoggingInState());
authService.login(
stateBefore.email,
stateBefore.password
).then(
(response) {
if (response.shouldValidateOTP) {
// 跳转到 OTP 页面
emit(const LoginNeedsOTPState());
// 恢复到之前的成功状态
emit(stateBefore);
} else {
// 直接登录
emit(const LoggedInState());
// 恢复到空闲状态
emit(const LoginIdleState.empty());
}
},
onError: (error, trace) {
// 记录错误并恢复到之前的成功状态
emit(LoginErrorState(error, trace));
emit(stateBefore);
}
);
}
}
}
3. ObjectCubit
ObjectCubit
是一个专门用于管理单个对象生命周期的 Cubit 实现。它可以确保你随时都能获取到最新版本的对象,而不需要依赖当前的状态。
示例:PostCubit
class PostCubit extends ObjectCubit<Post, APostState> {
PostCubit(Post post) : super(PostState(post));
[@override](/user/override)
Post? getObject(APostState state) {
if (state is PostState) {
return state.post;
} else {
return null;
}
}
[@override](/user/override)
void update(Post object) {
if (this.object == null) {
emit(PostState(object));
} else if (object.lastUpdate > this.object!.lastUpdate) {
emit(PostState(object));
}
}
}
4. CubitManager
CubitManager
是一个工厂类,用于管理特定类型的 Cubit。它通过为每个 Cubit 分配唯一的 ID 来确保在整个应用中只有一个 Cubit 实例与特定对象关联。
示例:PostCubitManager
class PostCubitManager extends CubitManager<PostCubit, Post, int> {
[@override](/user/override)
int buildId(Post object) {
return object.id;
}
[@override](/user/override)
PostCubit create(Post object) {
return PostCubit(object);
}
[@override](/user/override)
void updateCubit(PostCubit cubit, Post object) {
cubit.update(object);
}
}
5. ValueCubit
ValueCubit
是一个用于跟踪简单值的 Cubit。
示例:计数器 Cubit
final counterCubit = ValueCubit<int>(0);
counterCubit.set(1);
final counterResetCubit = InitialValueCubit<int>(0);
counterResetCubit.set(1);
counterResetCubit.reset(); // 恢复到初始值
6. 服务层
Potatoes
提供了多种服务类,帮助你更方便地管理 API 请求、偏好设置等。
ApiService & Dio 客户端
ApiService
是一个抽象类,代表 API 仓库。它提供了动态基础 URL、请求日志记录、授权头注入、响应解析等功能。
示例:创建自定义 ApiService
class Links extends potatoes.Links {
const Links();
[@override](/user/override)
String get devUrl => 'development url here';
[@override](/user/override)
String get productionUrl => 'staging/pre-prod url here';
[@override](/user/override)
String get stagingUrl => 'production url here';
}
final dio = potatoes.DioClient.instance(
preferencesService,
baseUrl: 'custom url to override Links.server',
connectTimeout: const Duration(seconds: 30),
sendTimeout: const Duration(seconds: 50),
receiveTimeout: const Duration(minutes: 5),
disableStatusesErrors: false
);
class CustomApiService extends ApiService {
const CustomApiService(super._dio);
[@override](/user/override)
Future<T> compute<T>(
Future<Response<dynamic>> request, {
String? mapperKey,
T Function(Map<String, dynamic> p1)? mapper,
T Function(String p1)? messageMapper
}) async {
try {
final response = await request;
// 解析响应并返回结果
} on DioException catch (error) {
throw ApiError.fromDio(error);
} catch (error, trace) {
throw ApiError.unknown(error.toString(), trace);
}
}
}
class AuthService extends CustomApiService {
const AuthService(super._dio);
Future<LoginResponse> login({
required String email,
required String password
}) {
return compute(
dio.post(
'/login',
data: {
'email': email,
'password': password
}
),
mapperKey: 'data',
mapper: LoginResponse.fromJson
);
}
}
PreferencesService
PreferencesService
是 SharedPreferences
的封装,提供了更好的偏好设置管理和额外的功能。
示例:创建自定义 PreferencesService
class AppPreferencesService extends PreferencesService {
static const String _tokenKey = 'token';
AppPreferencesService(super.preferences);
Future<void> saveToken(String value) => preferences.setString(_tokenKey, value);
[@override](/user/override)
FutureOr<Map<String, String>> getAuthHeaders() {
return {
'Authorization': preferences.getString(_tokenKey)!
};
}
}
7. 加载小部件
Potatoes
提供了两种自动加载的小部件:AutoListView
和 AutoContentView
,它们可以处理分页数据的自动更新。
AutoListView
AutoListView
用于显示分页列表,并处理空状态、加载状态、加载更多状态和错误状态。
示例:使用 AutoListView
AutoListView.get<Post>(
cubit: AutoListCubit(
provider: ({int page = 0}) => postService.getPosts(page: page)
),
itemBuilder: (context, post) => PostItem(post: post),
emptyBuilder: (context) => const Center(
child: Text("Empty list"),
),
errorBuilder: (context, retry) => Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text("An error occurred"),
TextButton(
onPressed: retry,
child: const Text("Retry"),
)
],
)
)
AutoContentView
AutoContentView
用于显示单个内容项,并处理其状态。
示例:使用 AutoContentView
AutoContentView.get<User>(
cubit: AutoContentCubit(
provider: userService.getUser(id: user.id),
),
builder: (context, user) => UserDisplay(user: user),
errorBuilder: (context, retry) => Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text("An error occurred"),
TextButton(
onPressed: retry,
child: const Text("Retry"),
)
],
)
)
8. 加载对话框
Potatoes
提供了两种加载对话框:全屏加载器和简单加载器。
示例:使用 CompletableMixin 和加载对话框
class MyScreen extends StatefulWidget {
const MyScreen({super.key});
[@override](/user/override)
State<MyScreen> createState() => _MyScreenState();
}
class _MyScreenState extends State<MyScreen> with CompletableMixin {
[@override](/user/override)
Widget build(BuildContext context) {
return const Placeholder();
}
void onEventReceived(BuildContext context, CustomState state) async {
await waitForDialog();
if (state is LoadingState) {
loadingDialogCompleter = showLoadingBarrier(context: context);
}
}
}
9. Phoenix
Phoenix
允许你通过调用 Phoenix.rebirth(context)
来完全重建整个应用程序。
示例:使用 Phoenix
void main() {
runApp(
const Phoenix(
child: MyApp()
)
);
}
// 重建整个应用程序
Phoenix.rebirth(context);
10. 库导入
Potatoes
依赖于以下包:
- Dio
- Equatable
- Flutter BLoC
- Shared Preferences
你可以通过以下方式导入这些包的类,而无需额外导入:
import 'package:potatoes/libs.dart';
11. 完整示例 Demo
以下是一个完整的示例应用程序,展示了如何使用 Potatoes
插件来构建一个简单的登录页面。
import 'package:flutter/material.dart';
import 'package:potatoes/potatoes.dart';
import 'package:potatoes/libs.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: const LoginPage(),
),
);
}
}
class LoginPage extends StatefulWidget {
const LoginPage({Key? key}) : super(key: key);
[@override](/user/override)
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> with CompletableMixin {
final LoginCubit _loginCubit = LoginCubit(AuthService(DioClient.instance(preferencesService)));
[@override](/user/override)
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => _loginCubit,
child: BlocListener<LoginCubit, LoginState>(
listener: (context, state) {
if (state is LoggingInState) {
loadingDialogCompleter = showLoadingBarrier(context: context);
} else if (state is LoginIdleState) {
waitForDialog();
} else if (state is LoginErrorState) {
waitForDialog();
// 处理错误
}
},
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
onChanged: (value) => _loginCubit.emit(LoginIdleState(value, '')),
decoration: const InputDecoration(labelText: 'Email'),
),
TextField(
onChanged: (value) => _loginCubit.emit(LoginIdleState('', value)),
decoration: const InputDecoration(labelText: 'Password'),
obscureText: true,
),
ElevatedButton(
onPressed: () => _loginCubit.login(),
child: const Text('Login'),
),
],
),
),
),
);
}
}
更多关于Flutter BLoC 模式的轻量级开发工具包插件potatoes的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html