Flutter BLoC 架构:timeCop从项目管理到计时系统的完整闭环

👉 这个模块的功能:
实现一个“项目 + 计时”的完整业务闭环:

用户可以新建项目,在项目下记录多个计时条目(开始、结束时间),并在界面中随时查看、编辑、删除。


🧩 二、模块结构总览(职责分层)

层级文件职责
UI 层页面(调用 Bloc)用户操作:点按钮、添加项目、删除项目
Bloc 层(业务编排)projects_bloc.dart处理事件 → 调用数据层 → 生成状态
事件层(输入)projects_event.dart定义用户行为:加载、创建、编辑、删除
状态层(输出)projects_state.dart定义页面数据:有哪些项目
数据层(DataProvider)data_provider.dart封装数据库 / API 的 CRUD 操作
模型层(实体)project.dart, timer_entry.dart定义数据结构与业务含义

📘 Bloc = 中间控制层。负责协调 “事件 → 状态”,是整个模块的「大脑」。

🚀 三、核心业务流程(从点击到界面更新)

我们举个例子——用户创建一个项目

[UI] 用户点击 “+ 新建项目”
   ↓
Bloc 接收到 CreateProject 事件
   ↓
调用 DataProvider.createProject() 在数据库中插入
   ↓
获取新项目对象 Project
   ↓
拼接到当前项目列表
   ↓
emit(ProjectsState(projects)) 发射新状态
   ↓
UI BlocBuilder 监听状态变化 → 自动刷新列表

📍这就是标准的 Flutter BLoC 数据流。
没有直接 setState(),而是状态驱动 UI


⚙️ 四、各文件详细解析(从下往上)

1️⃣ 模型层:TimerEntry

定义一条“计时记录”,属于某个项目。

class TimerEntry {
  final int id;
  final String? description;
  final int? projectID;   // 外键:属于哪个项目
  final DateTime startTime;
  final DateTime? endTime;
  final String? notes;
}

🔸 功能:

  • formatDuration():把时长转为 “HH:mm:ss” 格式

  • formatTime():计算当前条目的工作时长

📊 用途:
每个项目(Project)可以有多个 TimerEntry → 一对多关系。

比如:

ProjectTimerEntry
Flutter 开发09:00–11:00、14:00–15:30
写博客20:00–21:00

2️⃣ 数据访问层:DataProvider

定义所有数据库操作接口。

abstract class DataProvider {
  Future<Project> createProject(...);
  Future<List<Project>> listProjects();
  Future<void> editProject(Project project);
  Future<void> deleteProject(Project project);
  ...
}

🔹 特点:

  • 不关心底层(可接 SQLite、本地 JSON、云端 API);

  • 负责真正的数据读写;

  • 还支持 import()(数据迁移):
    把别的 DataProvider 的项目与计时数据导入当前系统。

🧠 类比后台开发:

它相当于 DAO 层(Data Access Object)。


3️⃣ 状态层:ProjectsState

表示页面上“当前有哪些项目”的状态。

class ProjectsState extends Equatable {
  final List<Project> projects;
}

🔸 特点:

  • 只读快照,每次 emit 都生成新对象;

  • Equatable 保证 Flutter 只在真正变化时刷新;

  • initial() 定义空状态。

🧠 类比后台开发:

它就像 Controller 最后返回给前端的响应体(Response)。


4️⃣ 事件层:ProjectsEvent

定义了所有可能的用户操作。

abstract class ProjectsEvent {}
class LoadProjects extends ProjectsEvent {}
class CreateProject extends ProjectsEvent { ... }
class EditProject extends ProjectsEvent { ... }
class DeleteProject extends ProjectsEvent { ... }

🧩 每个事件都是“用户意图”的抽象:

  • Load → 打开页面时加载数据;

  • Create → 点击“新建项目”;

  • Edit → 修改项目;

  • Delete → 删除项目。

🧠 类比后台开发:

它就像前端发到 Controller 的不同 API 请求。


5️⃣ Bloc 层:ProjectsBloc

整个模块的业务编排中心。

class ProjectsBloc extends Bloc<ProjectsEvent, ProjectsState> {
  final DataProvider data;

  ProjectsBloc(this.data) : super(ProjectsState.initial()) {
    on<LoadProjects>(...)   // 查询
    on<CreateProject>(...)  // 新增
    on<EditProject>(...)    // 修改
    on<DeleteProject>(...)  // 删除
  }
}

流程解析(以 Create 为例):

on<CreateProject>((event, emit) async {
  // 调用底层创建
  Project newProject = await data.createProject(name: event.name, colour: event.colour);
  // 克隆现有列表,插入新项目
  List<Project> projects = state.projects.map(Project.clone).toList();
  projects.add(newProject);
  projects.sort((a, b) => a.name.compareTo(b.name));
  // 发射新状态
  emit(ProjectsState(projects));
});

🧠 类比后台开发:

它相当于 Service 层:负责业务逻辑与数据协调。


6️⃣ 汇总 Barrel:bloc.dart

这是一个 “导出聚合文件”。

export 'projects_bloc.dart';
export 'projects_event.dart';
export 'projects_state.dart';

方便上层页面这样导入:

import 'package:timelens/blocs/projects/bloc.dart';

✅ 避免多文件重复 import。


📲 五、在页面中的实际使用

假设我们有一个 “项目列表页”:

class ProjectsPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => ProjectsBloc(MySqlDataProvider())..add(LoadProjects()),
      child: BlocBuilder<ProjectsBloc, ProjectsState>(
        builder: (context, state) {
          final projects = state.projects;
          return ListView.builder(
            itemCount: projects.length,
            itemBuilder: (_, i) => ListTile(
              title: Text(projects[i].name),
              trailing: IconButton(
                icon: Icon(Icons.delete),
                onPressed: () =>
                    context.read<ProjectsBloc>().add(DeleteProject(projects[i])),
              ),
            ),
          );
        },
      ),
    );
  }
}

🧠 流程:

  • 进入页面 → 发出 LoadProjects

  • Bloc 调用 listProjects()

  • 生成状态 → UI 自动渲染;

  • 用户点击删除 → 触发事件 → Bloc 删除数据 → emit 新状态 → UI 自动更新。


🔄 六、逻辑闭环图

┌────────────┐
│   UI层:按钮 │
└──────┬─────┘
       ↓ add()
┌────────────┐
│ Bloc:接收事件 │
│  调用 DataProvider │
└──────┬─────┘
       ↓ emit()
┌────────────┐
│ State:新状态 │
│  projects 列表变化 │
└──────┬─────┘
       ↓ rebuild()
┌────────────┐
│   UI层刷新 │
└────────────┘

✅ 七、总结

这套模块就是一个标准的 Flutter BLoC + Repository 架构实现:

  • Event:描述用户行为(输入)

  • Bloc:处理逻辑,协调数据与状态(中间层)

  • State:描述当前页面数据(输出)

  • DataProvider:封装持久化逻辑(底层)

  • Model:定义领域对象(实体)

整体实现了一个高内聚、低耦合、易扩展的项目管理 + 计时系统
未来可以很容易拓展:统计、同步云端、导出报表等功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值