Immich 移动端应用 Flutter 开发指南:目录结构、MVVM 架构、静态分析与代码生成工作流
本文基于 Immich 仓库的 mobile/README.md 展开,系统讲解 Immich 移动端应用(Flutter 实现)的工程组织方式:包括构建环境搭建、贡献前必须通过的静态分析检查、lib/ 目录的分层结构,以及 Model-View-ViewModel(MVVM)风格的模块架构规范。结合仓库中的依赖清单、mise 任务定义与分层文档,可以完整复现一套从环境准备到代码提交检查的移动端开发工作流。
应用定位与核心技术选型
Immich 移动端是一个基于 Flutter 的自托管照片/视频备份与浏览应用。mobile/README.md 给出的官方定位是:
The Immich mobile app is a Flutter-based solution leveraging the Isar Database for local storage and Riverpod for state management.
从当前仓库的依赖清单 mobile/pubspec.yaml 可以看到这套技术选型的实际落地:
- 状态管理:
hooks_riverpod(版本 ^2.6.1),即 Riverpod 与 Flutter Hooks 的结合,README 所称的 "Riverpod for state management" 在依赖中直接得到印证; - 本地存储:从依赖清单看,当前本地数据库已由 README 中提到的 Isar 演进为 Drift + SQLite 技术栈(
drift、sqlite3、sqlite_async、drift_sqlite_async)。仓库中 mobile/drift_schemas/main/ 目录保存了drift_schema_v1.json到drift_schema_v31.json共 31 个版本的结构快照,说明本地库结构经历过持续迭代,且每个 schema 变更都留有版本化记录(Drift 的迁移机制); - 平台通道:
pigeon(^26.3.4)用于类型安全的原生平台代码生成,mobile/pigeon/ 下定义了background_worker_api.dart、native_sync_api.dart、permission_api.dart、connectivity_api.dart等通道接口; - 后台同步:
worker_manager(^7.2.9)支撑 Android 端后台工作进程,lib/根目录下的wm_executor.dart即为该执行器入口; - 媒体与网络:
photo_manager(系统相册访问)、native_video_player(视频播放)、socket_io_client(与服务器实时通信)等,均为自托管媒体备份场景的关键能力。
此外,pubspec.yaml 中还有若干值得注意的工程细节:
name: immich_mobile
version: 3.2.0-rc.0+3020000
environment:
sdk: '>=3.12.0 <4.0.0'
flutter: 3.47.1
openapi依赖指向本地路径generated/openapi,即由 OpenAPI 规范自动生成的 REST 客户端(见下文代码生成工作流);native_video_player、socket_io_client、cupertino_http、ok_http均通过git:引用特定 commit 的 fork 版本,注释中注明了对应的上游 PR,属于典型的"为特定修复锁定 fork"的做法;analyzer.exclude排除了generated/**,避免对生成代码做静态分析。
环境搭建
mobile/README.md 将搭建细节指向官方开发者文档 docs/docs/developer/setup.md,其 Mobile App 章节给出的步骤为:
- 运行
mise //mobile:install安装 Flutter 依赖; - 运行
mise //mobile:translation生成翻译文件; - 进入
mobile/目录运行flutter run启动应用。
对照 mobile/mise.toml 的任务定义,可以更清楚地知道这些命令背后做了什么:
[tools."aqua:flutter/flutter"]
version = "3.47.1"
postinstall = "bash {{config_root}}/ios/scripts/xcode_flutter_patch.sh"
[tools."github:CQLabs/homebrew-dcm"]
version = "1.39.1"
bin = "dcm"
- mise 统一锁定 Flutter 3.47.1 与 DCM 1.39.1,保证所有贡献者使用一致的工具链版本;
install任务依赖//:open-api-dart(先生成 OpenAPI Dart 客户端),再执行flutter pub get,并在 macOS 上追加install:ios子任务执行 CocoaPodspod install:
[tasks.install]
alias = "install"
description = "Install flutter dependencies"
depends = ["//:open-api-dart"]
run = [
# This receives passed `--enforce-lockfile` args
"flutter pub get",
{ task = "install:ios" },
]
checkout任务定义了切换分支后的标准恢复动作:install→checkout:ios→codegen,即"装依赖 + 同步 iOS 工程 + 重新生成代码"一条龙,这对处理"换了分支后编译不过"的常见问题非常有用。
iOS 侧的补充说明(来自 setup 文档):本地自签名可通过创建 mobile/ios/Signing.local.xcconfig(该文件已 gitignore)覆盖 mobile/ios/Signing.xcconfig 中的 IMMICH_TEAM_ID、IMMICH_BUNDLE_ID_PROD、IMMICH_BUNDLE_ID_DEV、IMMICH_GROUP_ID 等值,避免每个贡献者重复改动共享配置。
首次连接服务器时,App 会询问要连接的 Immich 后端地址;如果不需要修改服务器代码,可以使用官方 demo 后端,也可以按 setup 文档启动本地开发栈(mise dev)后通过 http://your-machine-ip:3000 连接。
静态分析:贡献代码的硬性门槛
mobile/README.md 明确规定,以下静态分析检查必须全部通过,移动端的贡献才被视为有效:
dart format lib
dart analyze
dart run custom_lint
dcm analyze lib
其中前三条是 Dart/Flutter 生态的标准检查。第四条 dcm analyze lib 来自 DCM(Dart Code Metrics),这是一个需要手动下载的商业静态分析工具,README 对其使用做了完整说明,要点如下:
- Immich 项目获得了 DCM 的开源许可,但使用时有前提:你的账户不能存在活跃的免费层许可(可用
dcm license验证); - 如果你直接对 Immich 主仓库有写权限,在本地克隆中运行
dcm应直接可用; - 如果你在 fork 的克隆上工作,需要先把主仓库添加为 remote:
git remote add immich git@github.com:immich-app/immich.git
仓库中 mise 任务把这四条检查封装成了可组合的命令,实际执行的参数比 README 更严格:
[tasks."analyze:dart"]
run = "dart analyze --fatal-infos"
[tasks."analyze:dcm"]
run = "dcm analyze lib --fatal-style --fatal-warnings"
[tasks.format]
run = "dart format --set-exit-if-changed $(find lib -name '*.dart' \
-not \\( -name '*.g.dart' -o -name '*.drift.dart' -o -name '*.gr.dart' \\))"
可以看出:
dart analyze附带--fatal-infos,即 info 级别的提示也会失败;dcm analyze附带--fatal-style --fatal-warnings,风格与警告类问题同样视为失败;format任务对生成文件(*.g.dart、*.drift.dart、*.gr.dart)做了排除,并用--set-exit-if-changed保证未格式化会失败。
mobile/mise.toml 还定义了提交前的完整自检链 checklist:codegen:pigeon → codegen:dart → codegen:translation → analyze → format → test,覆盖了"生成代码、静态分析、格式化、跑测试"的全部环节。测试方面,test 任务执行 flutter test,测试代码位于 mobile/test/(按 unit、medium、drift 等分层组织),另有 mobile/integration_test/ 存放登录流程、失败同步恢复等端到端集成测试。
lib/ 目录结构
mobile/README.md 对 lib 目录的职责划分如下(逐条继承原文档说明):
constants:存放跨应用使用的关键常量,如颜色和 locale;extensions:增强既有功能的扩展方法集合,如asset_extensions.dart、string_extensions.dart;module_template:模块模板结构,包含 models、providers、services、ui、views 五个子分区,是新建模块的起点;modules:各功能模块的组织处,每个模块内部同样按 models/providers/services/ui/views 划分,以促进模块化开发与可扩展性;routing:路由与权限管理,包含auth_guard.dart、backup_permission_guard.dart等守卫,以及router.dart与router.gr.dart;shared:共享功能层,含缓存机制、公共模型、providers、services、UI 组件与视图;utils:工具类与函数集合,如async_mutex.dart、bytes_units.dart、debounce.dart、migration.dart等。
需要说明的是,README 描述的是模块化的目标组织方式;从当前源码树看,mobile/lib/ 已演进为更清晰的分层结构,顶层目录包括:
domain/(业务逻辑层)、data/(数据层)、presentation/(展示层)、providers/、services/、repositories/、models/、widgets/、pages/、routing/、extensions/、utils/等;- 其中
domain/有独立文档 mobile/lib/domain/README.md:domain 层承载业务逻辑,包含接口(interfaces)、模型(models)、服务(services)与工具(utils),并且永远不依赖 presentation 层或 infrastructure 层。presentation 层不应直接使用 repository,而应通过 domain service 交互:
// In presentation layer
final userService = ref.watch(userServiceProvider);
final user = await userService.getUser(userId);
路由层的实际文件也可以佐证 README 中"守卫 + 路由"的设计:mobile/lib/routing/ 下包含 router.dart、auth_guard.dart、locked_guard.dart、duplicate_guard.dart、app_navigation_observer.dart 与 custom_transition_builders.dart。其中 router.gr.dart(README 提及的文件)由 auto_route_generator 在代码生成阶段产出——mobile/mise.toml 的 codegen:dart 任务在 dart run build_runner build 之后专门执行了 dart format lib/routing/router.gr.dart,说明该文件是生成物而非手写。
MVVM 架构模式与分层规则
mobile/README.md 明确指出,Immich 的 Flutter 应用采用受 MVVM 启发的架构模式:模块按 models、providers、services、ui、views 组织,强调关注点分离,并再次强调新建模块请使用 module_template 模板。原文档对五类构件的职责说明如下,此处完整保留其语义:
- Models:应用的"蓝图"。它们是承载应用所需数据的容器,并负责管理这些数据的基础规则与逻辑,在整个应用中组织和复用信息;
- Providers(Riverpod):类似"交通管理员",让应用各部分高效地通信与共享信息,确保正确的时间把正确的数据送到正确的地方。所有与状态相关的内容都放在这里,底层机制是 Riverpod;
- Services:幕后的"工人",处理网络请求等重要任务及其他基础功能,独立工作、支撑应用主功能;
- UI:只关心外观与交互体验,不碰复杂内部逻辑,可复用的 widget 放在这里;
- Views:通过 Providers 获取所需数据并处理用户操作,屏蔽底层技术细节,Flutter 的屏幕与页面通常落在这里。
在 MVVM 之上,架构文档 补充了三条移动端特有的分层规则,与 lib/domain/README.md 相互印证,值得作为贡献准则重点掌握:
- Entities 与 Models 是两类不同的数据类:Entity 存入设备本地数据库(即上文 Drift + SQLite),Model 是临时的、只存在于内存中的;
- Repositories 是唯一允许在内部使用外部数据类(如 OpenAPI DTO)的地方,但其对外接口不得暴露这些外来数据类;
- 架构文档同时提醒:图示展示的是目标架构,现有代码库并不总是完全遵循,但新代码和贡献必须遵循该架构。
代码生成工作流
移动端工程的重代码生成由 mobile/mise.toml 统一编排,codegen 任务聚合了全部生成步骤:
[tasks.codegen]
alias = "codegen"
description = "Generate all codegen artifacts"
depends = [
"//:open-api-dart",
"codegen:dart",
"codegen:drift:migration",
"codegen:drift:schema",
"codegen:pigeon",
"codegen:translation",
]
各子任务对应的产物与命令为:
//:open-api-dart:从 open-api/immich-openapi-specs.json 生成 REST 客户端,产物落在全局generated/openapi(即pubspec.yaml中openapi依赖指向的路径)。架构文档指出,Web、Mobile、CLI 三个客户端都通过 OpenAPI 自动生成 REST 客户端;codegen:dart:执行dart run build_runner build,驱动 freezed、auto_route、Drift 代码等生成(输入源包括pubspec.yaml、build.yaml、lib/**/*.dart、lib/**/*.drift);codegen:drift:migration:执行dart run drift_dev make-migrations,基于 mobile/drift_schemas/main/ 的 schema 快照生成lib/data/db/main/database.steps.dart迁移步骤文件——这也解释了该目录下 31 个 JSON 快照的意义;codegen:drift:schema:从 schema 快照生成测试用的数据类与 companion 到test/drift/main/generated/,用于回归验证数据库结构;codegen:pigeon:对 mobile/pigeon/ 下每个 Dart 通道定义并行执行dart run pigeon --input {},生成类型安全的双端平台代码;codegen:translation:从仓库根目录 i18n/ 的 JSON 翻译文件生成多语言支持。流程上先dart run easy_localization:generate -S ../i18n生成 loader(lib/generated/codegen_loader.g.dart),再dart run bin/generate_keys.dart生成类型安全的翻译键(lib/generated/translations.g.dart)。setup 文档说明:新增文案应在i18n/en.json中加入键值对,然后运行mise //mobile:translation。
除上述核心链外,mise.toml 还提供了 codegen:watch(build_runner 监听模式)、codegen:app-icon(flutter_launcher_icons)与 codegen:splash(flutter_native_splash)等辅助任务,分别对应 pubspec.yaml 尾部的 flutter_launcher_icons: 与 flutter_native_splash.yaml 配置。
UI 组件与 Widget 预览
共享设计系统组件(按钮、输入、表单等)位于 mobile/packages/ui/ 包(即 pubspec.yaml 中的本地依赖 immich_ui),组件定义在 lib/src/components/,对应预览在 lib/src/previews/。setup 文档 指出,可以使用 Flutter 的 Widget Previewer 单独检视组件(带明暗切换与热重载):
cd mobile/packages/ui
flutter widget-preview start
在 VS Code 或 Android Studio 中打开 Flutter Widget Preview 侧栏标签时,预览器会自动启动。
贡献前检查清单
综合 mobile/README.md、mobile/mise.toml 与 setup 文档,一份可操作的移动端贡献前检查清单是:
- 环境:
mise trust+mise install(仓库根目录),然后mise //mobile:install、mise //mobile:translation; - 改了 schema / 通道 / 翻译后:运行
mise //mobile:codegen(或checkout任务)重新生成; - 静态分析全绿:
dart format lib、dart analyze(仓库内等价于--fatal-infos)、dart run custom_lint、dcm analyze lib(DCM 需按上文处理许可证,fork 场景先git remote add immich git@github.com:immich-app/immich.git); - 测试:
flutter test(mise //mobile:test),集成测试位于mobile/integration_test/; - 架构合规:新模块遵循 models / providers / services / ui / views 划分;新代码遵循 architecture 文档 中的目标架构,presentation 层经由 domain service 与 repositories 交互,OpenAPI DTO 只允许出现在 repository 内部。
按 README 的指引,更完整的贡献规范与架构细节请参考 docs/docs/developer/architecture.mdx(对应线上文档 Architecture 章节);搭建细节则以 docs/docs/developer/setup.md 为准。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00