首页
/ Immich 移动端应用 Flutter 开发指南:目录结构、MVVM 架构、静态分析与代码生成工作流

Immich 移动端应用 Flutter 开发指南:目录结构、MVVM 架构、静态分析与代码生成工作流

2026-09-06 15:27:46作者:伍希望

本文基于 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 技术栈(driftsqlite3sqlite_asyncdrift_sqlite_async)。仓库中 mobile/drift_schemas/main/ 目录保存了 drift_schema_v1.jsondrift_schema_v31.json 共 31 个版本的结构快照,说明本地库结构经历过持续迭代,且每个 schema 变更都留有版本化记录(Drift 的迁移机制);
  • 平台通道pigeon(^26.3.4)用于类型安全的原生平台代码生成,mobile/pigeon/ 下定义了 background_worker_api.dartnative_sync_api.dartpermission_api.dartconnectivity_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_playersocket_io_clientcupertino_httpok_http 均通过 git: 引用特定 commit 的 fork 版本,注释中注明了对应的上游 PR,属于典型的"为特定修复锁定 fork"的做法;
  • analyzer.exclude 排除了 generated/**,避免对生成代码做静态分析。

环境搭建

mobile/README.md 将搭建细节指向官方开发者文档 docs/docs/developer/setup.md,其 Mobile App 章节给出的步骤为:

  1. 运行 mise //mobile:install 安装 Flutter 依赖;
  2. 运行 mise //mobile:translation 生成翻译文件;
  3. 进入 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 子任务执行 CocoaPods pod 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 任务定义了切换分支后的标准恢复动作:installcheckout:ioscodegen,即"装依赖 + 同步 iOS 工程 + 重新生成代码"一条龙,这对处理"换了分支后编译不过"的常见问题非常有用。

iOS 侧的补充说明(来自 setup 文档):本地自签名可通过创建 mobile/ios/Signing.local.xcconfig(该文件已 gitignore)覆盖 mobile/ios/Signing.xcconfig 中的 IMMICH_TEAM_IDIMMICH_BUNDLE_ID_PRODIMMICH_BUNDLE_ID_DEVIMMICH_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 对其使用做了完整说明,要点如下:

  1. Immich 项目获得了 DCM 的开源许可,但使用时有前提:你的账户不能存在活跃的免费层许可(可用 dcm license 验证);
  2. 如果你直接对 Immich 主仓库有写权限,在本地克隆中运行 dcm 应直接可用;
  3. 如果你在 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 还定义了提交前的完整自检链 checklistcodegen:pigeoncodegen:dartcodegen:translationanalyzeformattest,覆盖了"生成代码、静态分析、格式化、跑测试"的全部环节。测试方面,test 任务执行 flutter test,测试代码位于 mobile/test/(按 unit、medium、drift 等分层组织),另有 mobile/integration_test/ 存放登录流程、失败同步恢复等端到端集成测试。

lib/ 目录结构

mobile/README.mdlib 目录的职责划分如下(逐条继承原文档说明):

  • constants:存放跨应用使用的关键常量,如颜色和 locale;
  • extensions:增强既有功能的扩展方法集合,如 asset_extensions.dartstring_extensions.dart
  • module_template:模块模板结构,包含 models、providers、services、ui、views 五个子分区,是新建模块的起点;
  • modules:各功能模块的组织处,每个模块内部同样按 models/providers/services/ui/views 划分,以促进模块化开发与可扩展性;
  • routing:路由与权限管理,包含 auth_guard.dartbackup_permission_guard.dart 等守卫,以及 router.dartrouter.gr.dart
  • shared:共享功能层,含缓存机制、公共模型、providers、services、UI 组件与视图;
  • utils:工具类与函数集合,如 async_mutex.dartbytes_units.dartdebounce.dartmigration.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.dartauth_guard.dartlocked_guard.dartduplicate_guard.dartapp_navigation_observer.dartcustom_transition_builders.dart。其中 router.gr.dart(README 提及的文件)由 auto_route_generator 在代码生成阶段产出——mobile/mise.tomlcodegen: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 相互印证,值得作为贡献准则重点掌握:

  1. Entities 与 Models 是两类不同的数据类:Entity 存入设备本地数据库(即上文 Drift + SQLite),Model 是临时的、只存在于内存中的;
  2. Repositories 是唯一允许在内部使用外部数据类(如 OpenAPI DTO)的地方,但其对外接口不得暴露这些外来数据类;
  3. 架构文档同时提醒:图示展示的是目标架构,现有代码库并不总是完全遵循,但新代码和贡献必须遵循该架构。

代码生成工作流

移动端工程的重代码生成由 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.yamlopenapi 依赖指向的路径)。架构文档指出,Web、Mobile、CLI 三个客户端都通过 OpenAPI 自动生成 REST 客户端;
  • codegen:dart:执行 dart run build_runner build,驱动 freezed、auto_route、Drift 代码等生成(输入源包括 pubspec.yamlbuild.yamllib/**/*.dartlib/**/*.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.mdmobile/mise.tomlsetup 文档,一份可操作的移动端贡献前检查清单是:

  1. 环境:mise trust + mise install(仓库根目录),然后 mise //mobile:installmise //mobile:translation
  2. 改了 schema / 通道 / 翻译后:运行 mise //mobile:codegen(或 checkout 任务)重新生成;
  3. 静态分析全绿:dart format libdart analyze(仓库内等价于 --fatal-infos)、dart run custom_lintdcm analyze lib(DCM 需按上文处理许可证,fork 场景先 git remote add immich git@github.com:immich-app/immich.git);
  4. 测试:flutter testmise //mobile:test),集成测试位于 mobile/integration_test/
  5. 架构合规:新模块遵循 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 为准。

登录后查看全文
热门项目推荐
相关项目推荐