You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Flutter Web应用中实现加载指示器并封装通用模块?

通用加载状态管理与API调用封装方案(Flutter Web)

核心方案

通过封装加载状态管理器和通用API调用工具,统一处理加载状态、请求流程与错误捕获,彻底避免在页面、容器、对话框中重复编写加载逻辑。


实现步骤

1. 轻量加载状态管理器

用ValueNotifier实现极简的状态管理,支持全局或局部加载状态控制:

class LoadingManager {
  final ValueNotifier<bool> _globalLoading = ValueNotifier(false);
  // 支持局部加载:用键值对管理不同组件的加载状态
  final Map<String, ValueNotifier<bool>> _localLoadings = {};

  ValueNotifier<bool> get globalLoading => _globalLoading;

  // 获取局部加载状态(不存在则创建)
  ValueNotifier<bool> getLocalLoading(String key) {
    return _localLoadings.putIfAbsent(key, () => ValueNotifier(false));
  }

  void setGlobalLoading(bool value) => _globalLoading.value = value;
  void setLocalLoading(String key, bool value) => getLocalLoading(key).value = value;
}

2. 通用API调用封装

结合加载状态管理,统一处理HTTP请求的加载、响应与错误:

import 'package:http/http.dart' as http; // 或用Dio,这里以原生http为例

class ApiClient {
  final LoadingManager loadingManager;
  final String baseUrl;

  ApiClient(this.loadingManager, this.baseUrl);

  // 全局加载的API调用
  Future<http.Response> callGlobalApi(
    Future<http.Response> Function() request,
  ) async {
    try {
      loadingManager.setGlobalLoading(true);
      return await request();
    } catch (e) {
      // 全局错误捕获:比如打印日志、统一弹窗提示
      rethrow; // 抛出错误让业务层处理具体逻辑
    } finally {
      loadingManager.setGlobalLoading(false);
    }
  }

  // 局部加载的API调用(指定key)
  Future<http.Response> callLocalApi(
    String loadingKey,
    Future<http.Response> Function() request,
  ) async {
    try {
      loadingManager.setLocalLoading(loadingKey, true);
      return await request();
    } catch (e) {
      rethrow;
    } finally {
      loadingManager.setLocalLoading(loadingKey, false);
    }
  }
}

3. 全局注入(可选)

用Provider或GetIt实现全局依赖注入,方便所有组件直接使用:

// main.dart初始化
void main() {
  final loadingManager = LoadingManager();
  final apiClient = ApiClient(loadingManager, 'https://your-backend-api.com');

  runApp(
    Provider<ApiClient>.value(
      value: apiClient,
      child: Provider<LoadingManager>.value(
        value: loadingManager,
        child: const MyApp(),
      ),
    ),
  );
}

4. 组件使用示例

页面/容器中用全局加载

class DataPage extends StatelessWidget {
  const DataPage({super.key});

  @override
  Widget build(BuildContext context) {
    final apiClient = Provider.of<ApiClient>(context);
    final loadingManager = Provider.of<LoadingManager>(context);

    return Scaffold(
      body: Stack(
        children: [
          Center(
            child: ElevatedButton(
              onPressed: () async {
                try {
                  final response = await apiClient.callGlobalApi(
                    () => http.get(Uri.parse('${apiClient.baseUrl}/data')),
                  );
                  // 处理成功响应
                } catch (e) {
                  // 处理业务错误
                }
              },
              child: const Text('获取数据'),
            ),
          ),
          // 全局加载遮罩
          ValueListenableBuilder<bool>(
            valueListenable: loadingManager.globalLoading,
            builder: (_, isLoading, __) {
              return isLoading
                  ? Container(
                      color: Colors.black54,
                      child: const Center(child: CircularProgressIndicator()),
                    )
                  : const SizedBox.shrink();
            },
          ),
        ],
      ),
    );
  }
}

对话框中用局部加载

void showSubmitDialog(BuildContext context) {
  const loadingKey = 'submit_dialog';
  final apiClient = Provider.of<ApiClient>(context, listen: false);
  final loadingManager = Provider.of<LoadingManager>(context, listen: false);

  showDialog(
    context: context,
    builder: (context) {
      return AlertDialog(
        title: const Text('提交数据'),
        content: ValueListenableBuilder<bool>(
          valueListenable: loadingManager.getLocalLoading(loadingKey),
          builder: (_, isLoading, __) {
            return isLoading
                ? const Center(child: CircularProgressIndicator())
                : const Text('确认提交吗?');
          },
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('取消'),
          ),
          TextButton(
            onPressed: () async {
              try {
                await apiClient.callLocalApi(
                  loadingKey,
                  () => http.post(
                    Uri.parse('${apiClient.baseUrl}/submit'),
                    body: {'content': 'test'},
                  ),
                );
                Navigator.pop(context);
                // 提示提交成功
              } catch (e) {
                // 提示提交失败
              }
            },
            child: const Text('确认'),
          ),
        ],
      );
    },
  );
}

额外优化建议

  • 封装通用LoadingOverlay组件,直接替换示例中的加载UI代码,进一步减少重复。
  • 在ApiClient中添加响应解析逻辑,比如自动将JSON转为实体类,避免业务层重复解析。
  • 局部加载的key可以用组件的hashCode或唯一标识,避免冲突。

内容的提问来源于stack exchange,提问作者Tony Lam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 07:22:36