本文参考Flutter鸿蒙开发指南(三):使用dio库实现网络请求-CSDN博客

本文记录操作过程以及遇到的问题

一、项目初始化

1.1 Android Studio创建项目

1.1.1 在终端创建osho项目

代码如下:

flutter create --platform ohos .

说明:命令中“.”表示在当前目录创建项目,--platform ohos指定适配鸿蒙平台,需确保本地已配置Flutter鸿蒙开发环境。

1.1.2 规范目录结构

整理文件目录结构,按功能模块分包文件,便于后续开发维护:

目录详解表

目录名

作用说明

api

存放所有网络请求相关代码,如接口封装、请求拦截、响应解析等

assets

存放静态资源,如图片、字体、JSON 配置文件等

components

存放可复用的公共 UI 组件,如按钮、卡片、输入框等

constants

存放全局常量,如接口域名、路由名称、枚举值、主题色等

pages

存放页面级组件,每个页面为一个独立文件或子目录

routes

存放路由配置,如路由表、路由拦截、页面跳转逻辑

stores

存放全局状态管理代码,如Provider、GetX、Bloc等

utils

存放工具类与通用函数,如日期格式化、加密、存储工具等

viewmodels

存放页面的视图模型(MVVM 模式),处理页面逻辑与数据交互

main.dart

项目入口文件

1.2 配置访问令牌、创建仓库

在这个网址申请新的令牌

访问令牌 - AtomGit | GitCode

然后在首页创建仓库

1.3 使用Git命令托管代码

1.3.1  Git Bash 常用命令

#1、仓库初始化
git init              # 在当前目录初始化 Git 仓库
git clone <仓库地址>   # 克隆远程仓库到本地

#2、配置身份
git config --global user.name "你的名字"
git config --global user.email "你的邮箱"
git config --global --list  # 查看全局配置


#3、提交代码
git add .             # 将所有修改文件加入暂存区
git add 文件名        # 单个文件加入暂存区
git commit -m "提交说明"  # 提交暂存区到本地仓库

#4、同步远程仓库
git pull origin 分支名 # 拉取远程分支代码到本地
git push origin 分支名 # 推送本地分支到远程仓库

#5、分支管理
git branch            # 查看本地所有分支
git branch 分支名     # 创建新分支
git checkout 分支名   # 切换到目标分支
git checkout -b 分支名 # 创建并切换到新分支
git merge 分支名      # 将目标分支合并到当前分支

#6、删除操作
git reset HEAD -- .# 清空暂存区所有文件的跟踪状态(回到add之前的状态)
rm -rf 文件名 # 彻底删除文件夹(含所有子文件)
rm -rf 文件名/包名 # 彻底删除包

#如果想删掉当前仓库(Desktop)下的所有文件(谨慎操作,建议先备份重要文件):
rm -rf * .[!.]* # 递归删除当前目录下所有文件/文件夹(Git Bash执行)

#7、验证操作结果
git status # 查看Git状态,正常会显示“working tree clean”(工作区干净)

#如果你不仅执行了git add,还执行了git commit(把文件提交到版本库),想撤销这次提交并删除文件:

git reset --soft HEAD~1 # 撤销最近一次提交(保留本地文件)
git reset HEAD -- . # 再执行清空暂存区

git reset --soft HEAD~1 # 撤销最近一次提交(保留本地文件)
git reset HEAD -- . # 再执行第一步清空暂存区

# 最后按需删除本地文件(参考删除操作)

1.3.2  用到的命令

git init 
git add . 
git commit -m 
git remote add origin <远程仓库地址> #<远程仓库地址>改成自己实际代码仓库,<>是不需要写
git push -u origin master 
git log

注意:文章开篇引用的文章中写的命令是:

git remote add origin master <远程仓库地址> 

有错误,master 是分支名,不能出现在 git remote add 命令中,仅在「推送 / 拉取 / 切换」分支时使用。

正确写法:

#先关联远程仓库(用 git remote add)
git remote add origin 你的远程仓库地址

#再推送 master 分支到远程 origin 别名对应的仓库
git push -u origin master

Git命令使用说明表

Git命令

核心作用(适配Flutter鸿蒙跨端开发场景)

补充说明(开发实操要点)

git init

初始化本地Git仓库,生成隐藏的.git文件夹,让当前Flutter鸿蒙项目目录被Git版本管理

仅在新建项目时执行一次,比如你创建Flutter鸿蒙跨端项目后,先执行该命令初始化仓库,后续所有代码变更都可被Git追踪

git add .

将本地工作目录中所有变更(新增 / 修改的Flutter页面、鸿蒙适配配置文件、组件代码等)添加到Git暂存区

开发中写完一个功能(比如抽离完无状态组件)后执行,. 代表全部文件,也可指定文件(如git add components/02_无状态组件.dart),仅暂存区文件能被commit

git commit -m "初始化仓库"

将暂存区的代码提交到本地仓库,生成版本记录;""内的备注可自定义,需清晰说明本次提交的任务(适配Flutter鸿蒙场景)

示例备注:

  1. git commit -m "初始化Flutter鸿蒙跨端项目结构"
  2. git commit -m "完成无状态组件抽离,适配鸿蒙样式"

备注要简洁,便于后续追溯版本变更

git remote add origin <远程仓库地址>

将本地Flutter鸿蒙项目仓库与远程Git仓库(如Gitee/GitHub)建立关联,origin是远程仓库的默认别名

执行前需先获取远程仓库地址(如HTTPS/SSH链接),关联后可通过origin快速操作远程仓库,无需重复输入完整地址;可执行git remote -v验证关联是否成功

git push -u origin master

将本地master分支的Flutter鸿蒙项目代码推送到远程仓库的master分支;-u建立本地与远程分支的跟踪关系

首次推送跨端项目代码时执行,后续推送只需输入git push;若远程仓库有初始文件(如README),需先git pull origin master拉取合并后再推送

git log

查看本地仓库的提交日志,包括每次提交的版本号、作者、时间、备注信息等

开发中可通过该命令追溯Flutter鸿蒙项目的变更记录,比如:

  1. 查看 “鸿蒙有状态组件” 是何时提交的
  2. 定位某段跨端适配代码的提交版本

常用参数:git log --oneline(精简日志)、git log -p(查看提交的代码差异)

1.3.3 输入命令

先输入命令:

git init 
git add . 
git commit -m 

这里遇到第一个问题,问题报错如下:

PS F:\Android\pros\xiuhu_mall> git commit -m "初始化项目"
Author identity unknown

*** Please tell me who you are.

Run

  git config --global user.email "you@example.com"
  git config --global user.name "Your Name"

to set your account's default identity.
Omit --global to set the identity only in this repository.

fatal: unable to auto-detect email address

这个报错是是 Git 提交代码时的身份认证错误,核心原因是还没给 Git 配置用户名和邮箱,导致 Git 无法识别提交者身份,所以拒绝执行 commit 操作。在https://atomgit.com/setting/绑定邮箱。

也可以通过桌面右键菜单里的Open Git Bash here命令行工具绑定

绑定后继续尝试,发现还是不行

打开项目文件位置,在项目文件位置处进入cmd,在cmd输入以下内容:

git config user.name "填你的用户名"
git config user.email "填你的邮箱"

重新提交git commit -m "初始化项目",成功解决

再输入命令:

git log #查看推送代码的日志

git remote add origin <远程仓库地址>
#地址可以在仓库复制

#修改已存在的远程仓库的地址(更新绑定令牌)
git remote set-url origin https://用户名:令牌@仓库地址/用户名/创仓库时的项目路径名.仓库后缀
#例子:git remote set-url origin https://D:123456789@gitlab.com/D/Project.git

git push -u origin master #最后执行推送仓库的命令

完成推送

二、使用Dio实现网络请求

2.1 添加依赖

在pubspec.yaml文件添加以下内容:

dio: ^5.5.0+1
provider: ^6.1.2

然后点击右上角的Pub get

2.2 接口说明

这里我举两个例子,一个是高德地图开放平台的天气接口,一个是IP 地址查询接口

2.2.1 高德地图开放平台的天气接

接口说明:

详情

接口地址

https://restapi.amap.com/v3/weather/weatherInfo

请求方式

GET

返回格式

JSON

请求示例

https://restapi.amap.com/v3/weather/weatherInfo?city=110101&key=e262**********

接口说明

高德地图免费天气查询接口,支持按城市编码 / 名称查询实时天气,无需申请密钥(使用公开测试 key)

请求参数说明:

名称

类型

必填

说明

city

string

城市编码 / 城市名称(示例:110101 = 北京市东城区,310101 = 上海市黄浦区)

key

string

高德开放平台 key(测试用公开 key:e262e59365f367738699546094269989)

extensions

string

选填

返回信息类型(base = 实时天气,all = 实时 + 预报,默认 base)

返回参数说明:

名称

类型

说明

status

string

请求状态(1 = 成功,0 = 失败)

lives

array

实时天气数组(单城市返回 1 条)

province

string

省份名称

city

string

城市名称

weather

string

天气状况(如:晴、多云)

temperature

string

实时气温(单位:℃)

winddirection

string

风向(如:东风)

windpower

string

风力(如:3 级)

完整的 Dio 请求示例:

#步骤 1:添加 Dio 依赖
dependencies:
  flutter:
    sdk: flutter
  dio: ^5.4.0  # 保持最新稳定版
#执行 flutter pub get 安装依赖。


#步骤 2:编写请求代码
#创建 weather_api_demo.dart 文件
import 'package:dio/dio.dart';

void main() async {
  // 1. 初始化 Dio 实例,添加基础配置
  Dio dio = Dio()
    ..options.baseUrl = 'https://restapi.amap.com/v3/weather' // 配置baseUrl,简化请求地址
    ..options.headers = {'Content-Type': 'application/json'}; // 设置请求头

  try {
    // 2. 构造请求参数(查询北京市东城区实时天气)
    Map<String, dynamic> params = {
      'city': '110101', // 北京市东城区编码
      'key': '1234567890', // 公开测试key(可自行申请)
      'extensions': 'base', // 只返回实时天气
    };

    // 3. 发送 GET 请求(使用baseUrl后,只需写接口路径)
    Response response = await dio.get(
      '/weatherInfo',
      queryParameters: params,
    );

    // 4. 处理响应数据
    if (response.statusCode == 200 && response.data['status'] == '1') {
      print('接口返回完整数据:${response.data}');
      
      // 解析实时天气数据(lives数组的第一个元素)
      Map<String, dynamic> weatherData = response.data['lives'][0];
      String province = weatherData['province'];
      String city = weatherData['city'];
      String weather = weatherData['weather'];
      String temp = weatherData['temperature'];
      String windDir = weatherData['winddirection'];
      String windPower = weatherData['windpower'];

      print('\n===== 解析后的天气信息 =====');
      print('地区:$province - $city');
      print('天气:$weather');
      print('气温:$temp ℃');
      print('风向/风力:$windDir $windPower');
    } else {
      print('请求失败:${response.data['info'] ?? '未知错误'}');
    }
  } on DioException catch (e) {
    // 5. 异常处理
    print('请求异常:');
    if (e.type == DioExceptionType.badResponse) {
      print('接口返回错误:${e.response?.data['info']}');
    } else if (e.type == DioExceptionType.connectionError) {
      print('网络连接错误,请检查网络');
    } else {
      print('错误详情:${e.message}');
    }
  }
}


#步骤 3:运行代码
#控制台会输出类似内容:
接口返回完整数据:{status: 1, count: 1, info: OK, infocode: 10000, lives: [{province: 北京, city: 东城区, weather: 晴, temperature: 18, winddirection: 北风, windpower: 3}]}

===== 解析后的天气信息 =====
地区:北京 - 东城区
天气:晴
气温:18 ℃
风向/风力:北风 3级

2.2.2 IP 地址查询接口

接口说明:

详情

接口地址

https://api.ipify.org?format=json(基础版)/

http://ip-api.com/json(详情版)

请求方式

GET

返回格式

JSON

请求示例

http://ip-api.com/json?lang=zh-CN&fields=query,country,regionName,city,isp

接口说明

免费的 IP 地址信息查询接口,无需密钥,可查询 IP 所属国家 / 地区 / 运营商等信息

请求参数说明:

名称

类型

必填

说明

lang

string

选填

返回语言(zh - CN = 中文,en = 英文,默认英文)

fields

string

选填

指定返回字段(用逗号分隔,减少冗余数据)

ip

string

选填

要查询的 IP 地址(不填则查询本机 IP)

返回参数说明:

名称

类型

说明

query

string

查询的 IP 地址

country

string

所属国家

regionName

string

所属省份 / 地区

city

string

所属城市

isp

string

网络运营商

status

string

请求状态(success = 成功)

完整的 Dio 请求示例:

#步骤 1:添加 Dio 依赖(同之前)
dependencies:
  flutter:
    sdk: flutter
  dio: ^5.4.0  # 保持最新稳定版

#步骤 2:编写请求代码
#创建 ip_api_demo.dart 文件
import 'package:dio/dio.dart';

void main() async {
  // 1. 初始化 Dio 实例(可配置超时时间等)
  Dio dio = Dio()
    ..options.connectTimeout = const Duration(seconds: 5) // 连接超时5秒
    ..options.receiveTimeout = const Duration(seconds: 3); // 接收超时3秒

  try {
    // 2. 构造请求参数(指定返回中文+核心字段)
    Map<String, dynamic> params = {
      'lang': 'zh-CN', // 返回中文结果
      'fields': 'query,country,regionName,city,isp', // 只返回需要的字段
      // 可选:指定查询的IP,比如 'ip': '8.8.8.8'(谷歌DNS)
    };

    // 3. 发送 GET 请求
    Response response = await dio.get(
      'http://ip-api.com/json',
      queryParameters: params,
    );

    // 4. 处理响应数据
    if (response.statusCode == 200 && response.data['status'] == 'success') {
      print('接口返回完整数据:${response.data}');
      
      // 解析核心字段
      String ip = response.data['query'];
      String country = response.data['country'];
      String province = response.data['regionName'];
      String city = response.data['city'];
      String isp = response.data['isp'];

      print('\n===== 解析后的IP地址信息 =====');
      print('查询IP:$ip');
      print('所属地区:$country > $province > $city');
      print('网络运营商:$isp');
    } else {
      print('请求成功但返回状态异常:${response.data}');
    }
  } on DioException catch (e) {
    // 5. 精细化异常捕获
    print('请求失败:');
    switch (e.type) {
      case DioExceptionType.connectionTimeout:
        print('错误类型:连接超时');
        break;
      case DioExceptionType.receiveTimeout:
        print('错误类型:接收超时');
        break;
      case DioExceptionType.badResponse:
        print('错误类型:响应异常');
        print('状态码:${e.response?.statusCode}');
        print('错误信息:${e.response?.data}');
        break;
      default:
        print('错误类型:${e.type}');
        print('错误描述:${e.message}');
    }
  }
}


#步骤 3:运行代码
#控制台会输出类似内容:
接口返回完整数据:{query: 114.114.114.114, country: 中国, regionName: 江苏省, city: 南京市, isp: 南京xxx有限公司, status: success}

===== 解析后的IP地址信息 =====
查询IP:114.114.114.114
所属地区:中国 > 江苏省 > 南京市
网络运营商:南京xxx有限公司

2.3 使用Dio进行网络请求并渲染UI

2.3.1 猫咪图片数据模型

文件路径:lib/viewmodels/cat_model.dart

作用:定义API返回的数据结构

代码如下:

class CatModel {
  final String id;
  final String url;
  final int width;
  final int height;
 
  CatModel({
    required this.id,
    required this.url,
    required this.width,
    required this.height,
  });
 
  factory CatModel.fromJson(Map<String, dynamic> json) {
    return CatModel(
      id: json['id'] ?? '',
      url: json['url'] ?? '',
      width: json['width'] ?? 0,
      height: json['height'] ?? 0,
    );
  }
 
  Map<String, dynamic> toJson() => {
    'id': id,
    'url': url,
    'width': width,
    'height': height,
  };
}
2.3.2 猫咪API服务

文件路径:lib/api/cat_api.dart

作用:封装具体的网络请求方法

代码如下:

import 'package:qing_mall/utils/http_util.dart';
import 'package:qing_mall/viewmodels/cat_model.dart';
import 'package:qing_mall/contents/api_constants.dart';
 
class CatApi {
  static final HttpUtil _http = HttpUtil();
 
  // 获取猫咪图片列表(基础方法)
  static Future<List<CatModel>> getCatImages({
    int limit = 1,  // 默认只获取1张
    int? page,
    String? order,
    bool? hasBreeds,
    String? breedIds,
    String? categoryIds,
    int? subId,
  }) async {
    try {
      final Map<String, dynamic> queryParams = {
        'limit': limit,
      };
 
      // 添加可选参数
      if (page != null) queryParams['page'] = page;
      if (order != null) queryParams['order'] = order;
      if (hasBreeds != null) queryParams['has_breeds'] = hasBreeds ? 1 : 0;
      if (breedIds != null) queryParams['breed_ids'] = breedIds;
      if (categoryIds != null) queryParams['category_ids'] = categoryIds;
      if (subId != null) queryParams['sub_id'] = subId;
 
      final data = await _http.get(
        '${ApiConstants.catBaseUrl}${ApiConstants.catImagesEndpoint}',
        queryParams: queryParams,
      );
 
      if (data is List) {
        return data.map((json) => CatModel.fromJson(json)).toList();
      } else {
        throw Exception('返回数据格式错误');
      }
    } catch (e) {
      rethrow;
    }
  }
 
  // 获取单张猫咪图片(简化方法)
  static Future<CatModel> getSingleCat() async {
    final cats = await getCatImages(limit: 1); // ✅ 调用上面定义的方法
    return cats.first;
  }
 
  // 可选:获取多张猫咪图片
  static Future<List<CatModel>> getMultipleCats(int count) async {
    return await getCatImages(limit: count);
  }
}
2.3.3 猫咪页面调用

文件:lib/pages/Cats/index.dart

作用:界面调用API并显示数据

代码如下:

import 'package:flutter/material.dart';
import 'package:qing_mall/api/cat_api.dart';
import 'package:qing_mall/viewmodels/cat_model.dart';
import 'package:qing_mall/components/cat_card.dart';
 
class CatsPage extends StatefulWidget {
  const CatsPage({super.key});
 
  @override
  State<CatsPage> createState() => _CatsPageState();
}
 
class _CatsPageState extends State<CatsPage> {
  CatModel? _cat; // ✅ 只保存一张图片
  bool _isLoading = false;
  String? _error;
 
  @override
  void initState() {
    super.initState();
    _fetchCat(); // ✅ 只获取一张
  }
 
  Future<void> _fetchCat() async {
    setState(() {
      _isLoading = true;
      _error = null;
    });
 
    try {
      final cat = await CatApi.getSingleCat(); // ✅ 调用获取单张的方法
      setState(() {
        _cat = cat;
      });
    } catch (e) {
      setState(() {
        _error = e.toString();
      });
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('随机猫咪 🐱'),
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: _fetchCat, // ✅ 刷新时也只获取一张
          ),
        ],
      ),
      body: _buildBody(),
    );
  }
 
  Widget _buildBody() {
    if (_isLoading) {
      return const Center(child: CircularProgressIndicator());
    }
 
    if (_error != null) {
      return Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(Icons.error_outline, size: 64, color: Colors.red),
            const SizedBox(height: 16),
            Text('加载失败: $_error'),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: _fetchCat,
              child: const Text('重新获取'),
            ),
          ],
        ),
      );
    }
 
    if (_cat == null) {
      return const Center(child: Text('暂无猫咪图片'));
    }
 
    // ✅ 只显示一张图片,居中显示
    return SingleChildScrollView(
      child: Center(
        child: Padding(
          padding: const EdgeInsets.all(20),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              CatCard(cat: _cat!),
              const SizedBox(height: 20),
              // 添加一些操作按钮
              Wrap(
                spacing: 12,
                children: [
                  ElevatedButton.icon(
                    onPressed: _fetchCat,
                    icon: const Icon(Icons.refresh),
                    label: const Text('换一张'),
                  ),
                  OutlinedButton.icon(
                    onPressed: () {
                      // 可以添加保存或分享功能
                    },
                    icon: const Icon(Icons.download),
                    label: const Text('保存'),
                  ),
                ],
              ),
              const SizedBox(height: 20),
              // 显示详细信息
              Container(
                padding: const EdgeInsets.all(16),
                decoration: BoxDecoration(
                  color: Colors.grey[50],
                  borderRadius: BorderRadius.circular(12),
                ),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text(
                      '图片信息',
                      style: TextStyle(
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    const SizedBox(height: 8),
                    Text('ID: ${_cat!.id}'),
                    Text('宽度: ${_cat!.width} 像素'),
                    Text('高度: ${_cat!.height} 像素'),
                    Text('URL: ${_cat!.url}'),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
2.3.4 猫咪卡片组件

文件:lib/components/cat_card.dart

作用:专门显示猫咪图片的UI组件

代码如下:

import 'package:flutter/material.dart';
import 'package:qing_mall/viewmodels/cat_model.dart';
 
class CatCard extends StatelessWidget {
  final CatModel cat;
 
  const CatCard({
    super.key,
    required this.cat,
  });
 
  @override
  Widget build(BuildContext context) {
    return Card(
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(12),
      ),
      elevation: 2,
      child: Column(
        children: [
          // 猫咪图片
          ClipRRect(
            borderRadius: const BorderRadius.vertical(
              top: Radius.circular(12),
            ),
            child: SizedBox(
              width: double.infinity,
              height: 200,
              child: Image.network(
                cat.url,
                fit: BoxFit.cover,
                loadingBuilder: (context, child, loadingProgress) {
                  if (loadingProgress == null) return child;
                  return Center(
                    child: CircularProgressIndicator(
                      value: loadingProgress.expectedTotalBytes != null
                          ? loadingProgress.cumulativeBytesLoaded /
                          loadingProgress.expectedTotalBytes!
                          : null,
                    ),
                  );
                },
                errorBuilder: (context, error, stackTrace) {
                  return Container(
                    color: Colors.grey[100],
                    child: const Center(
                      child: Icon(
                        Icons.image_not_supported,
                        size: 50,
                        color: Colors.grey,
                      ),
                    ),
                  );
                },
              ),
            ),
          ),
          // 猫咪信息
          Padding(
            padding: const EdgeInsets.all(12),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  '尺寸: ${cat.width} × ${cat.height}',
                  style: const TextStyle(
                    fontSize: 14,
                    fontWeight: FontWeight.w500,
                  ),
                ),
                const SizedBox(height: 4),
                Text(
                  'ID: ${cat.id}', // ✅ 修复:直接显示完整ID,不截取
                  style: TextStyle(
                    fontSize: 12,
                    color: Colors.grey[600],
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
2.3.5 路由配置文件

文件:lib/routes/index.dart

作用:管理页面之间的跳转关系

代码如下:

import 'package:flutter/material.dart';
import 'package:qing_mall/pages/Login/index.dart';
import 'package:qing_mall/pages/Main/index.dart';
import 'package:qing_mall/pages/Cats/index.dart';
 
// 管理路由
import 'package:flutter/material.dart';
import 'package:qing_mall/pages/Login/index.dart';
import 'package:qing_mall/pages/Main/index.dart';
import 'package:qing_mall/pages/Cats/index.dart';
 
// 返回App根组件
Widget getRootWidget() {
  return MaterialApp(
    // 命名路由
    initialRoute: '/', // 默认首页
    routes: getRootRoutes(),
  );
}
 
// 返回该App的路由配置
Map<String, Widget Function(BuildContext)> getRootRoutes() {
  return {
    '/': (context) => MainPage(), // 主页路由
    '/login': (context) => LoginPage(), // 登录路由
    '/cats': (context) => CatsPage(), // 猫咪图库路由
  };
}
2.3.6 猫咪API常量配置

文件:lib/contents/api_constants.dart

作用:集中管理API配置,避免硬编码,便于统一维护。

代码如下:

class ApiConstants {
  // 猫咪API
  static const String catBaseUrl = 'https://api.thecatapi.com/v1';
  static const String catImagesEndpoint = '/images/search';
 
  // 默认配置
  static const int defaultPageSize = 10;
  static const int defaultTimeout = 15; // 秒
}
2.3.7 封装Dio网络请求

文件:lib/utils/http_util.dart

作用:Dio网络请求工具类,封装单例和错误处理

代码如下:

import 'package:dio/dio.dart';
 
class HttpUtil {
  static final HttpUtil _instance = HttpUtil._internal();
  late Dio _dio;
 
  factory HttpUtil() => _instance;
 
  HttpUtil._internal() {
    _dio = Dio(BaseOptions(
      connectTimeout: const Duration(seconds: 15),
      receiveTimeout: const Duration(seconds: 15),
    ));
  }
 
  Dio get dio => _dio;
 
  // 通用GET请求
  Future<dynamic> get(String endpoint, {Map<String, dynamic>? queryParams}) async {
    try {
      final response = await _dio.get(endpoint, queryParameters: queryParams);
      return response.data;
    } on DioException catch (e) {
      throw _handleError(e);
    }
  }
 
  String _handleError(DioException e) {
    switch (e.type) {
      case DioExceptionType.connectionTimeout:
      case DioExceptionType.sendTimeout:
      case DioExceptionType.receiveTimeout:
        return '网络连接超时,请检查网络';
      case DioExceptionType.badResponse:
        return '服务器错误: ${e.response?.statusCode}';
      case DioExceptionType.cancel:
        return '请求已取消';
      default:
        return '网络错误: ${e.message}';
    }
  }
}
2.3.8 状态管理

文件:lib/stores/cat_store.dart

作用:负责获取、管理和更新猫咪图片数据

代码如下:

import 'package:flutter/material.dart';
import 'package:qing_mall/api/cat_api.dart';
import 'package:qing_mall/viewmodels/cat_model.dart';
 
class CatStore extends ChangeNotifier {
  List<CatModel> _cats = [];
  bool _isLoading = false;
  String? _error;
  int _currentPage = 1;
 
  List<CatModel> get cats => _cats;
  bool get isLoading => _isLoading;
  String? get error => _error;
  int get currentPage => _currentPage;
 
  // 获取猫咪图片
  Future<void> fetchCats({
    int limit = 10,
    bool hasBreeds = false,
    bool loadMore = false,
  }) async {
    if (!loadMore) {
      _currentPage = 1;
    }
 
    _isLoading = true;
    _error = null;
    notifyListeners();
 
    try {
      final newCats = await CatApi.getCatImages(
        limit: limit,
        page: _currentPage,
        hasBreeds: hasBreeds,
      );
 
      if (loadMore) {
        _cats.addAll(newCats);
      } else {
        _cats = newCats;
      }
 
      _currentPage++;
    } catch (e) {
      _error = e.toString();
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }
 
  // 刷新数据
  Future<void> refresh() async {
    await fetchCats(limit: _cats.length);
  }
 
  // 清空数据
  void clear() {
    _cats = [];
    _error = null;
    _currentPage = 1;
    notifyListeners();
  }
}
2.3.9 App入口主页

文件:lib/pages/Main/index.dart

作用:App的入口主页,用户从这里选择进入猫咪图库

代码如下:

import 'package:flutter/material.dart';
 
class MainPage extends StatelessWidget {
  const MainPage({super.key});
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Qing Mall'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              '欢迎使用 Qing Mall',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 40),
            // 猫咪图库入口
            SizedBox(
              width: 200,
              child: ElevatedButton.icon(
                onPressed: () {
                  Navigator.pushNamed(context, '/cats');
                },
                icon: const Icon(Icons.pets),
                label: const Text('猫咪图库'),
                style: ElevatedButton.styleFrom(
                  padding: const EdgeInsets.symmetric(vertical: 16),
                ),
              ),
            ),
            const SizedBox(height: 16),
            // 登录入口
            SizedBox(
              width: 200,
              child: OutlinedButton(
                onPressed: () {
                  Navigator.pushNamed(context, '/login');
                },
                child: const Text('用户登录'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
2.3.10 登录页面

文件:lib/pages/Login/index.dart

作用:登录页面的空壳子,有页面结构但需要填充具体登录功能

代码如下:

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
 
class LoginPage extends StatefulWidget {
  const LoginPage({super.key});
 
  @override
  State<LoginPage> createState() => _LoginPageState();
}
 
class _LoginPageState extends State<LoginPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('登录'),),
      body: Center(child: Text('登录页面'),),
    );
  }
}
2.3.11 主程序入口文件

文件:lib/main.dart

作用:应用启动入口,初始化

代码如下:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:qing_mall/routes/index.dart';
import 'package:qing_mall/stores/cat_store.dart';
 
void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => CatStore()),
        // 可以添加更多Provider
      ],
      child: getRootWidget(),
    ),
  );
}

2.4 运行项目

2.4.1 鸿蒙端

2.4.2 安卓端

三、总结与展望

3.1 遇到的问题

3.1.1 Git 提交代码时的身份认证错误

详见1.3.2与1.3.3。

3.1.2 Flutter项目在 Android Studio中无模拟器可选

在 Android Studio中开发 Flutter 项目时,会遇到设备选择列表里找不到 Android 模拟器的情况 ,如图所示,无法选择 Android 模拟器进行调试运行。

原因在于项目Android SDK未正确配置。AS 构建 Flutter 项目运行环境时,若缺失 Android SDK 相关配置,无法识别、关联 Android 模拟器,进而导致模拟器无法在设备列表中显示。

解决办法:

  1. 顶部菜单栏的 “File” ,选择 “Project Structure...,然后会弹出一个窗口。
  2. 点击Project Settings 分类下的Project,右侧会有“sdk” 选项,点击其下拉框,选择已安装的 Android SDK(需确保本地已正确安装对应 Android SDK)。
  3. 配置完成后,点击 Apply应用设置,再点击 OK。
  4. 回到 主界面,在设备选择区域Refresh一下,选择对应的模拟器即可运行Flutter 项目。

3.1.3 模拟器显示网络连接超时

解决办法:

  1. 在模拟器中,长按已连接的 WiFi,进入 网络和互联网 → Wi-Fi;​
  2. 点击 WiFi 旁的齿轮图标,进入详情页,选择 Forget(忘记网络);​
  3. 若仍异常,重启模拟器或检查本地防火墙是否拦截了模拟器网络请求。

3.2 展望

本文完成了 Flutter 鸿蒙项目从初始化、Git 版本管理到 Dio 网络请求封装、UI 渲染的实现,内容包括:

  • 按功能分层设计,提升代码可维护性与扩展性;​
  • 单例 Dio 工具类+异常统一处理,适配多场景请求;​
  • 同时支持鸿蒙、安卓端运行,体现 Flutter 跨平台优势;​
  • 针对性解决开发中常见的 Git、模拟器、网络问题。

后续可扩展方向:

  • 完善登录功能:添加账号密码验证、Token 存储与请求拦截器;​
  • 扩展猫咪图片功能:支持图片保存、分享、收藏等;​
  • 优化状态管理:结合 GetX 或 Bloc,提升复杂场景下的状态同步效率;​
  • 添加缓存机制:使用 SharedPreferences 缓存常用数据,减少网络请求。

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

Logo

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

更多推荐