Flutter手搓时间管理App Demo:从任务录入到专注计时的核心链路实现
1. 为什么我要手搓一个时间管理APP的Demo先交代背景。我在移动端开发这条线上摸爬滚打有些年头了做过工具类、社交类、效率类各种App但一直有个心结市面上绝大多数时间管理App要么功能堆到臃肿得像个企业级OA系统要么简单到只剩一个番茄钟计时器。真正能让人“愿意每天打开、打开后不焦虑、用完确实有产出”的产品少之又少。于是去年年底我给自己立了个小项目从零做一个时间管理类APP的Demo版本。注意是Demo不是要上架的商业产品。目标很明确——用最小的成本把“任务收集→优先级排序→时间块规划→执行反馈”这条核心链路跑通验证交互逻辑是否顺畅顺便把几个技术选型上的疑问一次性解决掉。这个Demo适合谁看如果你是独立开发者想快速验证一个效率工具的产品想法如果你是移动端新手想找一个有完整业务闭环的练手项目或者你只是对时间管理方法论感兴趣想看看一个App是怎么把GTD、番茄工作法、时间块这些概念落到代码里的——那这篇内容应该能给你不少参考。我全程用的是跨平台方案一套代码同时跑Android和iOS本地数据存储没有后端。整个Demo从设计到能跑通核心流程大概花了三周左右的业余时间。下面我把设计思路、技术选型、核心模块实现、踩过的坑全部摊开来讲。2. 整体设计与技术选型拆解2.1 核心功能边界怎么划做Demo最容易犯的错就是什么都想做。我一开始列了个功能清单写着写着发现已经能对标一个成熟商业产品了——任务管理、日历视图、番茄钟、数据统计、标签系统、提醒推送、云同步……这要是全做三个月都收不了尾。所以我给自己定了一条铁律Demo只验证一条主链路其余全部砍掉或做最简实现。最终确定的核心功能只有四个任务快速录入支持标题、预估时长、优先级三个字段其他一律不要今日视图按优先级和时间块展示当天要做的任务专注计时选中一个任务启动倒计时结束后记录实际用时简单回顾当天完成了几个任务、实际用时和预估用时的对比砍掉的功能包括账号体系、云同步、多设备、复杂标签、子任务、协作。这些不是不重要而是它们不影响主链路验证等核心逻辑跑通了再逐步加。这里有个经验Demo阶段每增加一个功能你的调试成本不是线性增长而是指数增长。因为功能之间会产生交互状态管理会迅速复杂化。宁可功能少而精也不要多而乱。2.2 技术选型背后的取舍逻辑技术选型我主要考虑三个维度开发效率、跨平台一致性、以及后续可扩展性。最终方案如下维度选型理由开发框架Flutter一套代码双端一致UI渲染性能好热重载开发快状态管理Provider轻量学习成本低Demo阶段够用不引入过度设计本地存储SQLitesqflite结构化数据支持复杂查询比键值对存储更适合任务数据时间处理系统原生DateTime intl不引入重型日期库减少依赖通知提醒flutter_local_notifications本地通知无需后端推送为什么不用React Native我两个都用过RN在复杂列表和动画上偶尔会有性能抖动而时间管理App里任务列表滚动、计时器动画都是高频操作Flutter的自绘引擎在这块更稳。为什么不用原生分别开发因为这是Demo双端各写一遍的时间成本我承担不起跨平台方案能让我把精力集中在业务逻辑上。状态管理这块我要多说一句。很多人一上来就上Riverpod、Bloc甚至自己搭一套架构。对于Demo来说这是典型的过度工程。Provider的ChangeNotifier模式对于“任务列表变化→UI刷新”这种场景简单直接代码量少调试也直观。等产品真的复杂到需要更细粒度的状态隔离时再迁移也不迟。2.3 数据模型设计的关键决策数据模型是整个App的地基设计不好后面改起来很痛苦。我最终设计了两个核心表任务表tasks字段包括id、title、priority0/1/2对应低中高、estimated_minutes、actual_minutes、status待办/进行中/已完成、created_at、completed_at、scheduled_date。专注记录表focus_sessions字段包括id、task_id、start_time、end_time、duration_minutes、completed是否完整走完计时。这里有个设计决策值得展开说为什么把专注记录单独建表而不是直接在任务表里加个字段因为一个任务可能被多次专注比如一个预估2小时的任务你可能分四次、每次30分钟来完成。单独建表能记录每一次专注的明细后续做统计时能算出“平均单次专注时长”“中断次数”这类有价值的指标。如果只存一个总时长这些信息就丢失了。另外priority我用整数0/1/2而不是字符串是为了排序方便——SQL里直接ORDER BY priority DESC就行不用做字符串映射。这种小细节在Demo阶段可能感觉不到好处但养成习惯后代码会干净很多。3. 核心模块的实操实现细节3.1 任务快速录入的交互设计任务录入看起来简单但它是用户最高频的操作体验好坏直接决定用户愿不愿意用。我的设计原则是三秒内完成一条任务录入。具体实现上我用了一个底部弹出的输入面板而不是跳转新页面。为什么因为跳转页面有转场动画心理上会觉得“重”而底部弹出面板感觉“轻”更符合快速记录的场景。面板里只有三个输入项标题文本输入、预估时长滑动选择15分钟到4小时、优先级三个按钮切换。这里有个细节标题输入框自动聚焦键盘弹起。用户打开App点一下加号直接就能打字不需要再点一下输入框。这个小小的优化实测能省掉用户大概0.5秒的操作时间但心理感受上的流畅度提升很明显。代码层面我用了一个showModalBottomSheet来承载输入面板配合TextEditingController管理输入状态。预估时长用Slider组件设置divisions为15因为范围是15到240分钟步长15分钟共15个档位。优先级用ToggleButtons三个选项互斥。// 预估时长滑块的核心配置 Slider( value: _estimatedMinutes.toDouble(), min: 15, max: 240, divisions: 15, label: ${_estimatedMinutes}分钟, onChanged: (value) { setState(() { _estimatedMinutes value.round(); }); }, )注意Slider的divisions参数如果不设置滑动是连续的用户很难精确选到想要的数值。设置成15之后每滑动一格就是15分钟手感清晰很多。这个参数很多人会忽略但对体验影响不小。3.2 今日视图的排序与分组逻辑今日视图是用户打开App后看到的第一个界面它要回答一个问题“我现在该做什么”所以排序逻辑至关重要。我的排序规则是这样的先按状态分组进行中的任务排最前然后是待办最后是已完成同组内按优先级降序优先级相同再按创建时间升序。这样用户一眼就能看到“正在做的事”和“接下来最该做的事”。分组展示上我用了一个ListView配合SliverStickyHeader让分组标题在滚动时吸顶。已完成的任务默认折叠点击才展开避免已完成任务占据太多视觉空间造成干扰。这里有个我踩过的坑一开始我按优先级排序结果高优先级的任务永远排在最上面用户每天看到的都是同样的几个“重要任务”反而产生了压迫感。后来我改成“进行中优先”只要用户开始了一个任务它就自动置顶这样视觉焦点跟着用户的实际行动走而不是跟着抽象的优先级走。这个改动之后我自己用起来舒服多了。排序的SQL实现大概是这样SELECT * FROM tasks WHERE scheduled_date ? ORDER BY CASE status WHEN doing THEN 0 WHEN todo THEN 1 WHEN done THEN 2 END, priority DESC, created_at ASC用CASE WHEN做自定义排序比在Dart层做排序更高效尤其是任务量大的时候。而且SQLite对CASE WHEN的支持很好不用担心兼容性。3.3 专注计时器的实现与状态保持专注计时是时间管理App的核心功能也是最容易出bug的地方。核心难点有三个计时精度、后台运行、状态恢复。计时精度方面我没有用Timer.periodic每秒减一的方式因为这种方式在App卡顿或系统调度延迟时会累积误差。我的做法是记录开始时间戳每次刷新时用当前时间 - 开始时间计算已过去多久然后算出剩余时间。这样即使刷新有延迟显示的时间也是准确的。// 计算剩余时间的核心逻辑 final elapsed DateTime.now().difference(_startTime); final remaining _totalDuration - elapsed; if (remaining Duration.zero) { _completeSession(); } else { setState(() { _remainingSeconds remaining.inSeconds; }); }后台运行方面Flutter的Timer在App进入后台后会被系统挂起这是所有移动端开发者都会遇到的问题。我的处理方式是在App生命周期回调里监听AppLifecycleState当App进入后台时记录当前时间戳回到前台时重新计算已经过去的时间更新计时器状态。这样即使用户切出去刷了十分钟消息再回来计时器显示的时间也是对的。状态恢复方面我把计时器的开始时间、总时长、关联任务ID存到了本地存储里。如果App被系统杀掉后重新打开能从存储里恢复计时状态。这个功能在Demo阶段其实可以不做但我还是加上了因为“计时到一半App崩了进度全丢”是用户最不能忍受的体验之一。实操心得计时器这类功能一定要用“时间戳差值”而不是“累加计数”的方式来实现。累加计数在理想环境下没问题但真实设备上系统调度、内存回收、后台限制都会导致计数不准。时间戳差值是幂等的无论中间发生什么结果都是对的。3.4 数据统计与回顾的轻量实现回顾功能我做得非常克制只展示三个数字今日完成任务数、今日专注总时长、预估准确率实际用时/预估用时的比值。预估准确率这个指标我觉得特别有价值。很多人做时间管理失败根本原因不是不努力而是对自己完成任务所需时间的预估严重偏离实际。有人觉得自己写一份报告只要1小时实际花了3小时然后产生挫败感。通过记录预估和实际的差异用户能逐渐校准自己的时间感知这才是时间管理App真正的价值所在。统计数据的查询我用了一条聚合SQL搞定SELECT COUNT(CASE WHEN status done THEN 1 END) as completed_count, SUM(actual_minutes) as total_minutes, AVG(CASE WHEN actual_minutes 0 THEN CAST(actual_minutes AS REAL) / estimated_minutes END) as accuracy_ratio FROM tasks WHERE scheduled_date ?用一条SQL算出所有指标比多次查询再在代码里合并要高效。而且SQLite的聚合函数性能很好即使几千条数据也是毫秒级返回。4. 开发过程中踩过的坑与排查实录4.1 常见问题速查表问题现象可能原因排查思路解决方案任务列表滚动卡顿列表项重建开销大用Flutter DevTools的Performance面板抓帧给列表项加const构造用ListView.builder懒加载计时器后台回来时间不对Timer被系统挂起打日志确认前后台切换时间点改用时间戳差值计算监听生命周期回调数据保存后重启App丢失数据库路径错误或未await检查getDatabasesPath返回值确保数据库初始化在App启动时完成操作加await通知不触发权限未申请或通道未创建检查系统通知权限和通知通道配置在App启动时申请权限创建高优先级通知通道优先级排序结果不符合预期排序字段类型不一致打印排序前后的数据统一用整数存储优先级SQL排序时确认字段类型4.2 几个让我印象深刻的坑第一个坑SQLite的并发写入问题。我在测试时发现快速连续添加多条任务时偶尔会有任务丢失。排查后发现是多个数据库操作并发执行导致的。SQLite在移动端默认是串行执行的但如果多个Future同时发起写操作可能会互相干扰。解决方案是用一个队列来串行化数据库写操作或者用事务包裹批量操作。第二个坑Flutter的热重载和状态管理冲突。开发过程中我频繁使用热重载但发现有时候热重载后Provider里的状态会重置导致界面显示异常。后来才明白热重载会保留Widget树但重建State如果状态存在StatefulWidget里而不是Provider里就会出现不一致。解决方案是把所有需要跨页面共享的状态都放到Provider里StatefulWidget只管理纯UI状态。第三个坑时间块的边界处理。我最初设计时间块时假设用户会严格按照规划执行。但实际使用中用户经常提前完成或超时。如果时间块是固定不可变的用户就会频繁手动调整体验很差。后来我改成了“软时间块”——时间块只是建议用户可以随时拖动调整系统会自动重新计算后续时间块的位置。这个改动让整个规划流程灵活了很多。避坑技巧Demo阶段一定要自己当用户每天用自己做的App。很多问题不是代码层面的而是交互逻辑层面的。比如我用了三天才发现每次完成任务后需要手动切换到“已完成”标签才能看到完成记录这个操作路径太长了。后来改成完成任务后自动在列表里标记为完成状态并淡出用户不需要任何额外操作。4.3 性能优化的几个实用手段Demo虽然用户量小但性能问题该注意还是要注意因为这些问题在产品化之后会被放大。列表渲染优化任务列表用ListView.builder而不是ListView前者只构建可见区域的item后者会一次性构建所有item。任务量上百条时差距非常明显。另外列表项的Widget尽量用const构造减少重建开销。数据库索引在scheduled_date和status字段上建了索引。虽然Demo数据量小查询快慢感觉不出来但养成建索引的习惯很重要。一条CREATE INDEX idx_tasks_date ON tasks(scheduled_date)就能让查询从全表扫描变成索引查找。图片和资源Demo里没有用图片资源所有图标都用Flutter内置的Material Icons。这既减小了包体积也避免了图片加载带来的性能开销。如果后续要加自定义图标建议用SVG而不是PNG矢量图在任何分辨率下都清晰而且文件更小。启动时间优化App启动时只做必要的初始化——数据库连接、Provider注册、通知权限申请。其他非紧急的初始化比如统计数据的预加载放到首帧渲染之后再做。这样用户感觉App启动很快实际上是把耗时操作延后了。5. 如果要把这个Demo继续做下去Demo跑通之后我认真想过后续的扩展方向。这里分享几个我觉得最有价值的方向也是我自己接下来可能会做的。第一个方向是数据导出与备份。现在数据全在本地SQLite里用户换设备就全丢了。最简单的方案是支持导出为CSV或JSON文件用户自己保存。稍微复杂一点的是接入系统级的备份机制。这个功能不需要后端实现成本低但对用户价值很大。第二个方向是时间感知的个性化校准。现在预估准确率只是一个展示指标如果能让它反过来影响用户体验就更有价值了。比如用户预估一个任务要1小时但系统根据历史数据发现他同类任务平均要1.5小时可以给出一个温和的提示。这种“系统比你更了解你自己”的体验是时间管理App的差异化竞争力。第三个方向是专注模式与系统联动。现在专注计时只是一个App内的计时器如果能在专注期间自动开启系统免打扰、屏蔽其他App通知那专注体验会好很多。这需要调用系统级的API不同平台实现方式不同但技术上可行。第四个方向是极简的数据可视化。现在回顾页面只有三个数字如果加一个简单的周视图柱状图展示每天专注时长的变化趋势用户能更直观地看到自己的进步。图表库我倾向于用轻量的fl_chart不引入重型图表方案。不过话说回来Demo阶段最重要的不是功能多而是核心链路是否顺畅。我现在每天用自己做的这个Demo管理任务虽然功能简陋但“录入→规划→专注→回顾”这条链路跑下来很顺没有明显的卡顿和逻辑断裂。这对我来说Demo的目标就已经达到了。最后分享一个我在做这个项目时最大的体会时间管理App的本质不是管理时间而是管理注意力。用户打开App的那一刻他的注意力是有限的、易碎的。每一个多余的点击、每一次不必要的等待、每一处让人困惑的交互都在消耗他的注意力。所以做这类产品克制比丰富更重要流畅比强大更重要。这个原则贯穿了我整个Demo的设计和开发过程也是我觉得最值得分享给同行的一点。