Flutter 鸿蒙化开发:用 Mock 数据服务解决网络 403 错误的实战方案*
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
摘要
在 Flutter for OpenHarmony 跨平台开发中,网络请求 403 错误是常见的开发障碍。本文针对该问题,从零实现了一套MockDataService模拟数据服务,无需依赖外部 API 即可完成下拉刷新、上拉加载等功能的验证,同时解决了鸿蒙设备上网络环境限制导致的接口访问失败问题,为鸿蒙 Flutter 开发提供了稳定的本地验证方案。
**一、开发痛点:
为什么 403 错误会卡住鸿蒙 Flutter 开发?
在鸿蒙设备上测试 Flutter 网络相关功能时,很多开发者都会遇到这类问题:
设备网络环境限制,无法访问公网 API,请求直接返回 403 Forbidden 错误;
第三方 API 存在访问频率限制或跨域校验,频繁测试后被限流;
依赖外部接口,调试时受网络波动、服务维护影响,无法稳定复现交互场景。
这些问题会直接导致你无法验证下拉刷新、上拉加载、错误重试等核心逻辑,严重拖慢开发效率。而解决这类问题的最佳方案,就是在开发阶段引入本地 Mock 数据服务,实现无网络依赖的功能验证。
二、Mock 数据服务实现
我们创建了MockDataService类,模拟分页数据接口,支持生成指定数量的 Post 数据,并内置了随机错误场景,用于测试异常处理逻辑:

import 'dart:async';
import 'dart:math';

class MockDataService {
  static final MockDataService instance = MockDataService._internal();
  MockDataService._internal();

  // 模拟分页数据请求
  Future<List<Post>> getPosts({required int page, required int limit}) async {
    // 模拟网络请求延迟
    await Future.delayed(const Duration(milliseconds: 800));

    // 模拟10%概率的请求失败,用于测试错误处理
    if (Random().nextDouble() < 0.1) {
      throw Exception('Mock 模拟请求失败(403场景)');
    }

    // 生成模拟数据
    List<Post> mockPosts = [];
    for (int i = 0; i < limit; i++) {
      int id = (page - 1) * limit + i + 1;
      mockPosts.add(Post(
        id: id,
        title: 'Mock 文章标题 #$id',
        body: '这是第$id条模拟数据的内容,用于鸿蒙设备上的Flutter列表刷新加载测试。',
      ));
    }
    return mockPosts;
  }
}

// 数据模型定义
class Post {
  final int id;
  final String title;
  final String body;

  Post({required this.id, required this.title, required this.body});
}

三、改造列表页面接入 Mock 服务
将之前的DataListPage改造为使用MockDataService,保留下拉刷新、上拉加载、错误重试等完整逻辑:

import 'package:flutter/material.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';

class DataListPage extends StatefulWidget {
  const DataListPage({super.key});

  @override
  State<DataListPage> createState() => _DataListPageState();
}

class _DataListPageState extends State<DataListPage> {
  final MockDataService _mockService = MockDataService.instance;
  final RefreshController _refreshController = RefreshController(initialRefresh: false);
  
  List<Post> posts = [];
  int _page = 1;
  final int _limit = 10;
  bool isLoading = false;
  bool hasMore = true;
  String? errorMessage;

  @override
  void initState() {
    super.initState();
    fetchPosts(isRefresh: true);
  }

  // 下拉刷新:重置页码,拉取第一页数据
  Future<void> fetchPosts({required bool isRefresh}) async {
    if (isRefresh) {
      setState(() {
        _page = 1;
        hasMore = true;
        errorMessage = null;
      });
    }
    setState(() {
      isLoading = true;
    });
    try {
      final newPosts = await _mockService.getPosts(page: _page, limit: _limit);
      setState(() {
        if (isRefresh) {
          posts = newPosts;
        } else {
          posts.addAll(newPosts);
        }
        // 模拟无更多数据场景(第5页后不再返回数据)
        hasMore = _page < 5;
      });
      // 通知刷新/加载完成
      if (isRefresh) {
        _refreshController.refreshCompleted();
      } else {
        _refreshController.loadComplete();
      }
    } catch (e) {
      setState(() {
        errorMessage = '请求出错:${e.toString()}';
      });
      if (isRefresh) {
        _refreshController.refreshFailed();
      } else {
        _refreshController.loadFailed();
      }
    } finally {
      setState(() {
        isLoading = false;
      });
    }
  }

  // 上拉加载更多:页码+1,拉取下一页数据
  Future<void> loadMorePosts() async {
    if (!hasMore || isLoading) return;
    setState(() {
      _page++;
    });
    await fetchPosts(isRefresh: false);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('鸿蒙Flutter Mock数据列表示例'),
      ),
      body: buildBody(),
    );
  }

  Widget buildBody() {
    // 首次加载中状态
    if (isLoading && posts.isEmpty) {
      return const Center(child: CircularProgressIndicator());
    }
    // 首次加载失败状态
    if (errorMessage != null && posts.isEmpty) {
      return Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(errorMessage!),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: () => fetchPosts(isRefresh: true), 
              child: const Text('重试')
            ),
          ],
        ),
      );
    }
    // 正常列表页面
    return SmartRefresher(
      controller: _refreshController,
      enablePullDown: true,
      enablePullUp: true,
      header: const WaterDropHeader(),
      footer: CustomFooter(
        builder: (BuildContext context, LoadStatus? mode) {
          Widget body;
          if (mode == LoadStatus.idle) {
            body = const Text("上拉加载更多");
          } else if (mode == LoadStatus.loading) {
            body = const CircularProgressIndicator();
          } else if (mode == LoadStatus.failed) {
            body = const Text("加载失败!点击重试");
          } else if (mode == LoadStatus.canLoading) {
            body = const Text("松手加载更多");
          } else {
            body = const Text("没有更多数据了");
          }
          return SizedBox(
            height: 55.0,
            child: Center(child: body),
          );
        },
      ),
      onRefresh: () => fetchPosts(isRefresh: true),
      onLoading: loadMorePosts,
      child: ListView.builder(
        itemCount: posts.length,
        itemBuilder: (context, index) {
          final post = posts[index];
          return Card(
            margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    'ID: ${post.id}',
                    style: const TextStyle(fontSize: 12, color: Colors.grey),
                  ),
                  const SizedBox(height: 8),
                  Text(
                    post.title,
                    style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 8),
                  Text(post.body),
                ],
              ),
            ),
          );
        },
      ),
    );
  }

  @override
  void dispose() {
    _refreshController.dispose();
    super.dispose();
  }
}

四、验证与运行效果
将项目编译并安装到鸿蒙设备后,应用成功运行,实现了以下效果:
1无网络环境下也能正常加载模拟数据,列表正常渲染
2下拉刷新触发数据重置,重新加载第一页模拟数据
3上拉加载可连续获取多页数据,第 5 页后显示 “没有更多数据”
4随机触发的模拟错误会触发错误提示与重试按钮,验证了异常处理逻辑
5所有交互逻辑(加载动画、状态提示)均在鸿蒙设备上稳定运行
(此处附上鸿蒙设备运行截图,包含正常加载、错误状态、无更多数据状态)
五、适配总结与扩展
方案优势
彻底规避网络依赖:无需依赖外部 API,不受鸿蒙设备网络限制影响
**完整的场景覆盖:**支持正常加载、加载失败、无更多数据等多种场景验证
开发效率提升:Mock 数据可自定义分页规则、错误概率,快速验证交互逻辑
**后续扩展方向
**
实现更复杂的 Mock 场景(如网络超时、数据为空、权限错误)
结合 JSON 文件预存 Mock 数据,实现离线测试
封装 Mock 服务与真实网络服务的切换开关,支持开发 / 生产环境一键切换
本文项目代码已托管至 AtomGit 平台,仓库地址:https://atomgit.com/your-repo/flutter-oh-mock-demo

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐