核心任务

为开源鸿蒙跨平台工程集成网络请求能力,实现数据清单列表的完整构建与开源鸿蒙设备运行验证。


前言

随着开源鸿蒙(OpenHarmony)生态的持续发展,跨平台技术在鸿蒙设备上的应用场景日益广泛。本文以 "上海著名景点清单列表" 为实际需求,无需复杂代码堆砌,聚焦开发思路与核心流程,详解如何通过 Flutter 技术栈快速实现网络数据拉取、列表展示及鸿蒙设备适配,帮助开发者高效完成跨平台应用落地。

一、环境与权限配置

打开 VS Code,点击顶部 “终端”→“新建终端”

首先在终端输入以下命令,将http package添加到依赖中:

flutter pub add http

出现如下问题

这个报错的意思是Flutter 没识别到当前目录是有效的工程根目录,导致无法执行flutter pub add命令,重新用flutter create创建,解决如下

现在工程已经成功创建完成。

在当前终端中执行命令,用 VS Code 打开工程:

code shanghai_scenic_list

在 VS Code 左侧「资源管理器」中,找到工程根目录下的pubspec.yaml文件,双击打开;

替换文件内容为以下代码

name: shanghai_scenic_list
description: A new Flutter project for OpenHarmony.
version: 1.0.0+1

environment:
  sdk: '>=3.10.0 <4.0.0'

dependencies:
  flutter:
    sdk: flutter
  dio: ^5.4.3       # 网络请求库
  json_annotation: ^4.9.0  # JSON解析库
  cached_network_image: ^3.3.0  # 图片加载库

dev_dependencies:
  flutter_test:
    sdk: flutter
  json_serializable: ^6.8.1  # JSON解析代码生成
  build_runner: ^2.4.8       # 代码生成工具
  flutter_lints: ^3.0.0

flutter:
  uses-material-design: true

如下

然后在终端执行命令加载依赖

flutter pub get

我出现了这个问题

这是一个不兼容错误,需要修改 pubspec.yaml 里的 Dart SDK 版本限制,才能让依赖正常加载。

解决步骤

  1. 打开 pubspec.yaml 文件在 VS Code 的左侧资源管理器中,找到工程根目录下的 pubspec.yaml 并双击打开。

  2. 找到文件开头的 environment 部分:把它改成当前 SDK 匹配的版本:

environment:
  sdk: '>=3.4.0 <4.0.0'

保存文件。

又出现了以下问题

这是依赖版本冲突错误,需要调整 json_serializable 的版本来解决

在终端里输入这条命令,把 json_serializable 降级到兼容 3.4.0 的版本

flutter pub add dev:json_serializable:^6.8.0

如下则运行成功

二、创建数据模型文件

在 VS Code 左侧资源管理器中,找到 lib 文件夹

右键 lib → 新建文件夹→ 命名为 models(小写,无空格)

右键 models 文件夹 → 新建文件→ 命名为 scenic_spot_model.dart

双击打开文件,粘贴以下代码

import 'package:json_annotation/json_annotation.dart';

// 这行是自动生成代码的关联,必须保留
part 'scenic_spot_model.g.dart';

// JSON解析模型类
@JsonSerializable()
class ScenicSpotModel {
  final int id;
  final String name;
  final String address;
  final double rating;
  final String intro;
  @JsonKey(name: 'imageUrl') // 适配接口字段名
  final String imageUrl;

  // 构造函数
  ScenicSpotModel({
    required this.id,
    required this.name,
    required this.address,
    required this.rating,
    required this.intro,
    required this.imageUrl,
  });

  // 从JSON转模型
  factory ScenicSpotModel.fromJson(Map<String, dynamic> json) = _$ScenicSpotModelFromJson;
  // 模型转JSON
  Map<String, dynamic> toJson() => _$ScenicSpotModelToJson(this);
}

保存文件。

接着生成 JSON 解析代码,在 VS Code 终端中,输入命令

flutter pub run build_runner build

此时 models 文件夹下会多出 scenic_spot_model.g.dart 文件

三、创建网络请求工具

右键 lib → 新建文件夹→ 命名为 api

右键 api → 新建文件→ 命名为 api_service.dart

打开文件,粘贴以下代码

import 'package:dio/dio.dart';
import '../models/scenic_spot_model.dart';

// 单例模式的网络请求类
class ApiService {
  static final ApiService _instance = ApiService._internal();
  factory ApiService() => _instance;
  late Dio _dio;

  // 私有构造函数
  ApiService._internal() {
    _dio = Dio()
      ..options.baseUrl = "https://mock.apifox.cn/m1/2986939-0-default" // 测试接口
      ..options.connectTimeout = const Duration(seconds: 15) // 超时时间
      ..interceptors.add(LogInterceptor(responseBody: true)); // 打印日志
  }

  // 获取上海景点列表
  Future<List<ScenicSpotModel>> getShanghaiScenicSpots() async {
    try {
      final response = await _dio.get("/shanghai/scenic-spots");
      if (response.statusCode == 200) {
        List<dynamic> dataList = response.data;
        return dataList.map((e) => ScenicSpotModel.fromJson(e)).toList();
      } else {
        throw Exception("请求失败,状态码:${response.statusCode}");
      }
    } on DioException catch (e) {
      String errorMsg = "网络请求失败:";
      if (e.type == DioExceptionType.connectionTimeout) errorMsg += "连接超时";
      else if (e.type == DioExceptionType.badResponse) errorMsg += "服务器错误";
      else errorMsg += e.message ?? "未知错误";
      throw Exception(errorMsg);
    } catch (e) {
      throw Exception("获取数据失败:$e");
    }
  }
}

四、创建景点列表页面

在 VS Code 左侧资源管理器中,右键 lib → 新建文件夹→ 命名为 pages

右键 pages → 新建文件→ 命名为 scenic_list_page.dart

打开文件,粘贴以下的代码

import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import '../api/api_service.dart';
import '../models/scenic_spot_model.dart';

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

  @override
  State<ScenicListPage> createState() => _ScenicListPageState();
}

class _ScenicListPageState extends State<ScenicListPage> {
  List<ScenicSpotModel> _spotList = []; // 景点列表数据
  bool _isLoading = true; // 加载状态
  String? _errorMsg; // 错误信息
  final ApiService _api = ApiService(); // 网络请求实例

  // 页面初始化时加载数据
  @override
  void initState() {
    super.initState();
    _loadScenicData();
  }

  // 加载景点数据(封装成独立方法)
  Future<void> _loadScenicData() async {
    setState(() {
      _isLoading = true;
      _errorMsg = null;
    });

    try {
      final data = await _api.getShanghaiScenicSpots();
      setState(() => _spotList = data);
    } catch (e) {
      setState(() => _errorMsg = e.toString());
    } finally {
      setState(() => _isLoading = false);
    }
  }

  // 构建单个景点卡片(独立方法,代码更清晰)
  Widget _buildScenicItem(ScenicSpotModel spot) {
    return Card(
      margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
      elevation: 2, // 增加卡片阴影,更美观
      child: InkWell(
        onTap: () {
          // 点击卡片弹出提示
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text("${spot.name} - 评分:${spot.rating}")),
          );
        },
        child: Padding(
          padding: const EdgeInsets.all(12),
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              // 景点图片(带加载/错误占位)
              CachedNetworkImage(
                imageUrl: spot.imageUrl,
                width: 80,
                height: 80,
                fit: BoxFit.cover,
                placeholder: (context, url) => const Center(
                  child: CircularProgressIndicator(strokeWidth: 2),
                ),
                errorWidget: (context, url, error) => const Icon(
                  Icons.broken_image,
                  size: 80,
                  color: Colors.grey,
                ),
              ),
              const SizedBox(width: 12),
              // 景点信息(占满剩余宽度)
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      spot.name,
                      style: const TextStyle(
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    const SizedBox(height: 4),
                    Text(
                      spot.address,
                      style: const TextStyle(
                        color: Colors.grey,
                        fontSize: 12,
                      ),
                    ),
                    const SizedBox(height: 4),
                    Row(
                      children: [
                        const Icon(
                          Icons.star,
                          color: Colors.amber,
                          size: 14,
                        ),
                        const SizedBox(width: 4),
                        Text(
                          "${spot.rating}",
                          style: const TextStyle(fontSize: 12),
                        ),
                      ],
                    ),
                    const SizedBox(height: 4),
                    Text(
                      spot.intro,
                      maxLines: 2,
                      overflow: TextOverflow.ellipsis,
                      style: const TextStyle(fontSize: 12),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  // 构建页面主体(根据状态切换显示内容)
  Widget _buildPageContent() {
    if (_isLoading) {
      // 加载中:居中显示加载圈
      return const Center(child: CircularProgressIndicator());
    } else if (_errorMsg != null) {
      // 加载失败:显示错误+重试按钮
      return Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              _errorMsg!,
              style: const TextStyle(color: Colors.red, fontSize: 14),
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: _loadScenicData,
              child: const Text("重新加载"),
            ),
          ],
        ),
      );
    } else if (_spotList.isEmpty) {
      // 无数据:显示空提示
      return const Center(child: Text("暂无景点数据"));
    } else {
      // 加载成功:显示列表
      return ListView.builder(
        padding: const EdgeInsets.symmetric(vertical: 8),
        itemCount: _spotList.length,
        itemBuilder: (context, index) => _buildScenicItem(_spotList[index]),
      );
    }
  }

  // 页面主构建方法
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("上海著名景点"),
        centerTitle: true, // 标题居中
      ),
      body: _buildPageContent(),
      // 刷新按钮
      floatingActionButton: FloatingActionButton(
        onPressed: _loadScenicData,
        child: const Icon(Icons.refresh),
      ),
    );
  }
}

保存文件。

接着在 lib 文件夹下找到 main.dart

将原有内容替换为以下代码:

import 'package:flutter/material.dart';
import 'pages/scenic_list_page.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "上海景点清单",
      // 主题配置
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const ScenicListPage(), // 设置首页为景点列表
      debugShowCheckedModeBanner: false, // 隐藏调试标签
    );
  }
}

保存文件。

打开DevEco Studio→File→Open→打开文件下的ohos,如下

运行如下


总结

  • 网络能力与权限配置:熟练掌握开源鸿蒙跨平台工程使用原生网络请求API的规范,完成网络权限的声明与配置,确保工程具备合法且稳定的网络访问能力。
  • 数据清单列表构建:基于网络请求返回的数据,完成清单列表的完整开发,包括数据解析、列表渲染、空数据/异常数据兜底展示,确保列表功能完整、交互流畅。
  • 可选拓展(三方库接入):可选用适配开源鸿蒙跨平台技术栈网络请求三方库实现上述能力,需掌握不同技术栈三方库的集成流程、版本适配规则及差异化使用要点。
    1. React Native技术栈:推荐使用axios(通用型HTTP请求库,支持拦截器、请求取消、响应转换)。OpenHarmony已兼容三方库清单
    2. Flutter技术栈:推荐使用dio(Flutter生态主流请求库,支持拦截器、FormData、Cookie管理)、http(Flutter官方轻量库,无冗余依赖)、chopper(基于注解生成请求代码,便于大型项目维护);接入时需重点关注三方库与开源鸿蒙SDK版本的兼容性、跨平台请求稳定性,以及鸿蒙权限体系对三方库网络访问的限制适配。OpenHarmony已兼容三方库清单
  • 开源鸿蒙终端运行验证代码提交规范:确保添加网络请求能力的工程在开源鸿蒙真机/开发板/模拟器上能正常运行,数据清单列表可正确加载、展示网络请求返回的数据。将完整工程代码(含工程配置文件、源码、资源文件、调试日志)按 Git 提交规范(清晰的 commit message、合理的提交粒度)推送到 AtomGit 公开仓库,确保仓库代码可直接拉取并复现运行效果。

    欢迎加入开源鸿蒙跨平台社区 https://openharmonycrossplatform.csdn.net

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐