Appearance
2026 生产级参考架构指南:Android (Compose/XML) 与 Flutter 最优解
回到总览:状态管理、路由、架构
相关模块:Compose / Flutter / Vue 状态管理对照 · Riverpod / Provider / Bloc / GetX 对比 · ViewModel 架构、SavedStateHandle 与进程被杀恢复
一句话定义
生产级参考架构(Reference Architecture)是指在大型移动端项目中,为避免“业务逻辑与 UI 耦合”、“数据源混乱”及“多端状态不一致”,综合官方规范与当前生态最优解所制定的单向数据流 (UDF)、分层解耦与不可变数据模型的标准架构范式。
代码索引
计划补齐的实验:
labs/shared/state-routing/reference-architecture-demo/— Flutter Riverpod+Freezed 范式与 Android Compose MVI 统一参考架构 Demo
为什么需要
- 为什么在大型 App 开发中必须建立统一的“参考架构(Reference Architecture)”?
- 一句话答:如果没有统一的分层架构,团队成员会随意在 UI 层直接发网络请求、在 ViewModel 内部操作数据库,导致代码难以编写单元测试、状态变化不可追踪且难以重构。
- Flutter 2026 年最成熟落地的推荐分层架构是什么?
- 一句话答:采用 UI 层 (
ConsumerWidget) → ViewModel 层 (Riverpod AsyncNotifier) → Repository 门面层 → DataSource 层 (Dio+Isar/Hive) → 不可变 Model (freezed) 的 Clean Architecture 范式。
- 一句话答:采用 UI 层 (
- Android Compose 与传统 XML 在架构模式上有何演进差异?
- 一句话答:传统 XML 采用 MVVM 模式(ViewModel 通过 LiveData/StateFlow 暴露独立属性,View 双向绑定);而 Compose 拥抱 MVI / UDF (单向数据流) 模式(ViewModel 暴露单一不可变
UiState密封类,UI 发送UiEventIntent)。
- 一句话答:传统 XML 采用 MVVM 模式(ViewModel 通过 LiveData/StateFlow 暴露独立属性,View 双向绑定);而 Compose 拥抱 MVI / UDF (单向数据流) 模式(ViewModel 暴露单一不可变
架构对比全景
1. Flutter 2026 生产级最优解分层架构
图注补充:1. UI 展示层 (Views / Widgets);2. 视图模型层 (ViewModels / Controllers);Riverpod AsyncNotifierProvider / StateNotifier;3. 数据仓库层 (Repositories);Repository (数据唯入口, 协调缓存与网络);Remote DataSource (Dio / Retrofit);Local DataSource (Isar / Hive / Sqflite);freezed + json_serializable 不可变实体;1. 监听 State / 发起 Event;5. 返回 JSON 反序列化。
Flutter 官方与生态推荐技术栈表 (2026 最优解)
| 官方架构层级 | 具体技术栈推荐 (2026 最优解) | 职责说明 |
|---|---|---|
| UI 层 (Views) | StatelessWidget / ConsumerWidget | 纯 UI 展示,无状态,不包含任何业务与网络逻辑 |
| UI 层 (ViewModels) | Riverpod (NotifierProvider / AsyncNotifier) | 桥接 UI 和数据,管理加载/成功/失败 AsyncValue 状态 |
| 数据层 (Repositories) | Riverpod (作为依赖注入 DI 容器) | 协调网络和本地缓存,是数据的唯一入口,屏蔽数据源细节 |
| 数据层 (DataSources) | Dio (网络) + Isar / Hive (本地持久化) | 纯粹的数据读写工具,无业务逻辑,支持拦截器与加密 |
| 数据模型 (Models) | freezed + json_serializable | 生成 100% 不可变 (Immutable) 的实体类与深拷贝 copyWith |
2. Android 架构演进:Compose (MVI) vs XML (MVVM)
图注补充:1. Intent / UiEvent;2. 单一不可变 UiState (StateFlow);1. User Action;2. 多 LiveData / DataBinding。
Android 架构对比表
| 维度 | Android Compose 架构 (MVI / UDF) | Android 传统 XML 架构 (MVVM) |
|---|---|---|
| 状态流向 | 严格单向数据流 (Unidirectional Data Flow) | 响应式双向/多向数据绑定 |
| UI 状态容器 | StateFlow<UiState> (包装为单个不可变 Sealed Class) | 多个独立的 MutableLiveData<T> 字段 |
| 组件解耦 | State Hoisting (状态提升) 为 Stateless Composable | 通过 DataBinding 或 ViewBinding 绑定 XML |
| 依赖注入 | Hilt (Dagger) / Koin(4.0 详解) | Hilt / Dagger2 |
| 网络与数据库 | Retrofit / Ktor + Room | Retrofit + Room |
代码范式
1. Flutter 2026 推荐代码规范 (Riverpod + Freezed)
dart
// 1. Model 层:使用 freezed 生成不可变数据模型
@freezed
class UserProfile with _$UserProfile {
const factory UserProfile({
required String id,
required String name,
required String avatarUrl,
}) = _UserProfile;
factory UserProfile.fromJson(Map<String, dynamic> json) => _$UserProfileFromJson(json);
}
// 2. ViewModel 层:使用 Riverpod AsyncNotifier 管理异步状态
@riverpod
class UserProfileNotifier extends _$UserProfileNotifier {
@override
Future<UserProfile> build(String userId) async {
// 自动处理 Loading / Data / Error 状态
final repository = ref.watch(userRepositoryProvider);
return repository.getUserProfile(userId);
}
Future<void> updateName(String newName) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
final repository = ref.read(userRepositoryProvider);
return repository.updateName(newName);
});
}
}
// 3. UI 层:ConsumerWidget 响应式渲染
class UserProfileScreen extends ConsumerWidget {
final String userId;
const UserProfileScreen({super.key, required this.userId});
@override
Widget build(BuildContext context, WidgetRef ref) {
final profileAsync = ref.watch(userProfileNotifierProvider(userId));
return profileAsync.when(
data: (profile) => Text("Hello, ${profile.name}"),
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text("Error: $err"),
);
}
}2. Android Compose 推荐代码规范 (MVI + StateFlow)
kotlin
// 1. 单一不可变 UiState 声明
sealed interface UserUiState {
object Loading : UserUiState
data class Success(val user: User) : UserUiState
data class Error(val message: String) : UserUiState
}
// 2. ViewModel 暴露单向数据流
@HiltViewModel
class UserViewModel @Inject constructor(
private val repository: UserRepository
) : ViewModel() {
private val _uiState = MutableStateFlow<UserUiState>(UserUiState.Loading)
val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
fun loadUser(userId: String) {
viewModelScope.launch {
_uiState.value = UserUiState.Loading
try {
val user = repository.getUser(userId)
_uiState.value = UserUiState.Success(user)
} catch (e: Exception) {
_uiState.value = UserUiState.Error(e.message ?: "Unknown Error")
}
}
}
}常见误配、事故后果与排障
1. 事故:在 UI 层直接通过 Dio / Retrofit 发网络请求
- 误配原因:为了图方便,直接在
onClick或build()中调用Dio().get(...)。 - 后果:破坏了分层架构;当网络失败或返回数据格式改变时,需要修改 UI 代码,且无法对 UI 进行自动化单元测试!
- 排障与修法:所有网络请求必须收口至 DataSource 与 Repository,UI 层只能通过 ViewModel 的方法间接调用。
复习检查题
在 Flutter 2026 最成熟的推荐架构中,使用
freezed生成不可变数据模型 (Immutable Model) 有什么好处?答:使用
freezed生成的不可变模型天然支持==值的相等性比较与深拷贝copyWith。它能保证数据在传递过程中不会被任何组件意外修改(Side Effects),极大地提升了状态变更的可追溯性,并使得 Riverpod 等状态管理能够精准识别数据是否真的发生改变,从而避免无效刷新。Android Compose 推荐的 MVI (UDF) 模式与传统 XML 的 MVVM 模式最核心的区别是什么?
答:最核心的区别在于状态的集中性与单向流向。传统 MVVM 的 ViewModel 通常暴露多个独立的 LiveData 变量,容易导致状态组合混乱;而 MVI 模式将页面的所有状态收口包装为一个单一的不可变
UiState(通常是 Sealed Class 密封类),UI 只能通过发送UiEvent触发 ViewModel 产生新的UiState,实现了绝对单向的数据流动。
速记
- Flutter 分层:Widget 看界面,Notifier 管状态,Repo 作门面,Dio/Isar 做读写,Freezed 锁数据。
- Android 架构:Compose 选 MVI 结合 StateFlow 密封类,XML 选 MVVM 配合 LiveData/StateFlow。
- 红线底线:UI 绝不直接查 DB/网络,所有数据流动必须经过 Repository 与 ViewModel。