Flutter BLoC 模式的轻量级开发工具包插件potatoes的使用

发布于 1周前 作者 ionicwang 最后一次编辑是 5天前 来自 Flutter

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

PreferencesServiceSharedPreferences 的封装,提供了更好的偏好设置管理和额外的功能。

示例:创建自定义 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 提供了两种自动加载的小部件:AutoListViewAutoContentView,它们可以处理分页数据的自动更新。

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

回到顶部