Flutter 列表 + Riverpod 架构实战 —— 从 setState 到状态驱动列表的工程落地

Flutter 列表真正的威力,不在 ListView,而在:

👉 列表 = 状态的投影结果


一、为什么 Flutter 列表必须配状态管理?

新手写法:

Dart 复制代码
setState(() {
  list.addAll(newData);
});

问题:

  • 业务逻辑混在 UI
  • 无法复用
  • 无法测试
  • 分页、错误、加载状态混乱

企业级目标:

👉 UI 只关心"状态"

👉 所有列表行为 = 状态机

二、列表的标准状态建模(架构核心)

一个工程级列表,最少包含:

Dart 复制代码
class ListState<T> {
  final List<T> items;
  final bool isLoading;
  final bool isRefreshing;
  final bool hasMore;
  final String? error;

  const ListState({
    this.items = const [],
    this.isLoading = false,
    this.isRefreshing = false,
    this.hasMore = true,
    this.error,
  });
}

👉 这是 Flutter 列表的"领域模型(Domain State)"。

三、Riverpod Controller 层(替代 ViewModel)

Dart 复制代码
class UserListController extends StateNotifier<ListState<User>> {
  UserListController() : super(const ListState());

  Future<void> refresh() async {
    state = state.copyWith(isRefreshing: true);
    final data = await repo.fetch(page: 1);
    state = state.copyWith(
      items: data,
      isRefreshing: false,
      hasMore: true,
    );
  }

  Future<void> loadMore() async {
    if (!state.hasMore || state.isLoading) return;

    state = state.copyWith(isLoading: true);
    final data = await repo.fetchNext();
    state = state.copyWith(
      items: [...state.items, ...data],
      isLoading: false,
      hasMore: data.isNotEmpty,
    );
  }
}
Dart 复制代码
final userListProvider =
  StateNotifierProvider<UserListController, ListState<User>>(
    (ref) => UserListController(),
  );

👉 这里就是 MVVM / MVI 的 ViewModel / Reducer 层。

四、UI 层(纯渲染,不写业务)

Dart 复制代码
class UserListPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(userListProvider);
    final controller = ref.read(userListProvider.notifier);

    return RefreshIndicator(
      onRefresh: controller.refresh,
      child: ListView.builder(
        itemCount: state.items.length + 1,
        itemBuilder: (context, index) {
          if (index == state.items.length) {
            return _Footer(state);
          }
          return UserItem(state.items[index]);
        },
      ),
    );
  }
}
Dart 复制代码
class _Footer extends StatelessWidget {
  final ListState state;

  const _Footer(this.state);

  @override
  Widget build(BuildContext context) {
    if (state.isLoading) {
      return Center(child: CircularProgressIndicator());
    }
    if (!state.hasMore) {
      return Center(child: Text("没有更多了"));
    }
    return SizedBox.shrink();
  }
}

✔ UI = 纯函数

✔ 可测试

✔ 可替换

✔ 可维护

五、滚动触底触发分页

Dart 复制代码
final scrollController = ScrollController();

scrollController.addListener(() {
  if (scrollController.position.pixels >
      scrollController.position.maxScrollExtent - 200) {
    ref.read(userListProvider.notifier).loadMore();
  }
});

👉 滚动是输入

👉 Controller 决定状态变化

👉 UI 只是结果

六、这套架构解决了什么?

问题 解决
setState 混乱 状态集中管理
分页难写 行为模型化
异常难处理 error 状态
UI 不可测 Controller 可单测
列表难复用 Domain State 可复用

👉 这是"架构级列表"。

七、架构总结

Flutter 列表的终极形式:

Dart 复制代码
Input(滚动/点击)
   ↓
Controller(Riverpod)
   ↓
State(列表领域模型)
   ↓
ListView(UI渲染)
相关推荐
恋猫de小郭3 小时前
Flutter 的文字为什么比 SwiftUI 更细?问题主要在 Skia 的 256 项查找表里
android·前端·flutter
好的佩奇21 小时前
Dart 之类型系统
flutter·dart
恋猫de小郭1 天前
Dart 3.15 新特性:this 终于支持类型提升了
android·前端·flutter
FungLeo2 天前
成为全栈·Flutter App 篇·主题与可访问性:深浅色、大字号和触控体验
flutter·移动开发·深色模式·可访问性·成为全栈·大字号适配
用户95421573334853 天前
只是选个照片,怎么留下了这么多访问记录?Flutter 图片选择器排查与优化实录
flutter
FungLeo3 天前
成为全栈·Flutter App 篇·第四批复盘:复用了什么,移动端又新增了什么
flutter·移动开发·跨端开发·成为全栈·api契约·全栈复盘
jason.zeng@15022073 天前
使用vscode+flutter开发android程序(入门)
android·vscode·flutter
jason.zeng@15022073 天前
Flutter 项目标准分层目录(repository/dto/page,对齐后端思想)
flutter
程序员老刘5 天前
跨平台开发地图 | 2026年10月
flutter·ai编程·客户端
林瞅瞅5 天前
Flutter 开发环境从零搭建与调试记录
flutter