Flutter 应用怎样管理页面和异步状态
以教学资料库解释Flutter组件身份、页面导航和异步请求顺序,建立本地仓库并处理旧响应、失败和页面销毁。
实现资料列表与详情,验证快速切换查询时旧响应被丢弃,并在断网重启后恢复已保存内容。
教学资料库有分类列表、搜索结果和正文页。用户输入新关键词时,前一个请求可能还没结束;后发请求先完成,旧结果又晚到,列表就会倒退。本课用这个问题解释 Flutter 的组件状态与异步边界。
资料采用合成 ID,网络层先返回测试数据,最终再换为受控服务。第一版只支持列表、详情、保存本地副本与重试,暂不加入跨设备账户和复杂推荐。
Widget 描述与资料身份
Widget 是界面的不可变描述。Flutter 的 element 保存树中的身份与关联,render object 参与布局和绘制。重建 Widget 不等于每个像素都重新绘制;是否需要布局或绘制由具体变化决定。不能把重建次数直接当作性能结论。
StatefulWidget 的 State 有自己的生命周期,资料记录不应依赖某次组件实例存活。列表条目以资料 ID 作为逻辑身份;标题和位置都可能变化。需要 key 时选择能表达这个身份的值,避免用随机值让框架无法保留应保留的状态。
界面状态包括加载、空结果、成功和失败。已存在的列表正在刷新时,可以保留旧内容并显示刷新提示,不必清空整页。选中资料由 ID 表示,正文读取失败只影响详情,不让其他已保存资料消失。
仓库与异步对象的职责
Dart 的 Future 表达以后完成的一次结果或错误,Stream 表达连续事件。网络请求通常适合 Future,资料库的持续变化可以作为事件流表达。两者都不会自动把 CPU 密集计算并行化;大量解析需要评估 isolate 等机制的使用条件。
仓库提供读取列表、读取正文、保存副本和重试操作。它维护持久数据与请求信息,页面发送意图并展示状态。不要让两个页面分别缓存可修改的同一条资料,否则一边删除后另一边仍可能把它显示为可离线。
取消、错误与完成都要经过同一个状态提交规则。请求失败应保留可识别的错误,不把异常变成空列表;空列表表示查询成功但无结果。页面关闭后不能再随意更新已销毁的 State,既要处理生命周期,也要判断结果是否仍属于当前请求。
建立可以运行的 Flutter 工程
安装与目标平台兼容的 Flutter 工具链,在终端先执行 flutter doctor 查看缺少的依赖,再用 flutter create reading_library 建立工程。记录 SDK 与目标设备信息,运行 flutter run 确认模板能启动。这些是供读者执行的工程步骤,本文没有声称已运行该应用。
将代码按资料模型、仓库、列表页和详情页分开。先放两份文本资源,通过工程资源配置加入应用,仓库读取合成目录并返回列表。实现列表选择、详情正文与返回路径,导航只传资料 ID,详情自己根据 ID 读取当前记录。
接着选择目标平台支持的本地持久化方案,记录插件与原生配置。保存副本后写入索引,重启从索引恢复。先验证资源读取与持久化,再接异步搜索。不要把 Web 上的路径或桌面目录规则直接搬到移动端;沙盒、文件选择和权限需要适配器处理。
晚到结果怎样被丢弃
每次查询递增请求序号,结果返回时仅允许最新序号提交。关键词变化也可以加入防抖,但防抖只减少请求数量,不解决所有响应乱序。真正的网络取消可以节省资源,仍需要结果校验,因为取消可能发生在响应已产生之后。
下面离线 Python 例子模拟请求 2 已经成为当前请求,请求 1 的结果先被拒绝,请求 2 被接受。它不包含 Dart Future,也没有取消 HTTP 请求或运行 Flutter。
latest_request = 2
library = []
def finish(request_id, items):
global library
if request_id != latest_request:
return False
library = list(items)
return True
assert not finish(1, ["old"])
assert finish(2, ["new"])
assert library == ["new"]
print(library)
输出 ['new']。被拒绝的旧响应没有修改 library,断言验证最终列表。真实应用还要让请求错误与加载结束遵守相同序号规则,否则旧请求的异常可能把新请求成功后的页面改为失败。
页面销毁后判断是否仍可提交 UI 更新;仓库可以继续完成它拥有的保存任务。mounted 检查解决生命周期的一部分,不能替代最新请求判断。页面仍挂载时,同样可能已经切换到另一个关键词。
本地状态与平台适配
本地索引至少保存 ID、标题、内容版本与副本位置。只有完整写入并可读取的副本才标为可离线。把目录加载和正文读取分开,让损坏的一篇资料不必阻止整个资料库打开。
网络失败时允许用户打开已保存内容,未保存资料显示离线不可用。下载失败保留已有版本,记录失败原因和重试入口。用户删除本地副本后更新仓库,再让页面派生新的状态,避免只移除文件却保留可离线标签。
桌面、移动和浏览器的窗口尺寸、返回操作、键盘和文件能力不同。共享 Widget 树可以复用布局,但要在目标环境检查长标题、文字缩放、键盘遮挡和返回路径。插件支持某个平台也不代表无需配置系统权限或构建设置。
正文较长时还要检查列表和阅读区域的滚动职责。目录页按可见需求构建条目,详情保持可恢复的阅读位置,不把正文拆成每次刷新都改变身份的随机组件。搜索失败后保留输入关键词,用户可以改词或重试,不能要求重新进入页面。
适配器给资料排序定义稳定规则,同标题资料按ID等确定字段区分。分页响应需要查询身份和页标识,刷新后旧页不应追加进新列表。测试最后一页、空页和重试同一页,重复资料只保留一次;这套分页协议需要服务端与客户端共同定义,不能由Widget自行猜测。
练习:验证请求与页面恢复
为搜索仓库加入可控延迟,让旧请求晚于新请求完成,再加入旧请求失败的情况。快速输入不同关键词后,最终只显示当前关键词的结果和错误,加载提示不会被旧事件提前关闭。
保存两份正文,退出后断网重开,应能读到两份。删除其中一份副本,其列表项应保留明确的不可离线状态。退出详情时延迟请求继续返回,应用不出现销毁后更新错误。记录这些实际观察值,再扩展分类和分页;小模型只能验证此次请求顺序契约。
资料来源与延伸阅读
资料核验:2026-11-10。本文为原创工程讲解,教学示例不代表线上实测。