Flutter 鸿蒙:获取真实轮播图API数据

首先,欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net,获取更多Flutter鸿蒙开发相关教程、技术支持和开源资源,与开发者们一起交流学习、共同进步。

本文参考Flutter鸿蒙开发指南(八):获取轮播图数据-CSDN博客

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

在上一节中,我们完成了轮播图的细节优化,实现了「轮播图顶部搜索框」和「底部导航指示灯」两个核心交互组件。这一节,我们将聚焦核心需求------获取真实的轮播图API数据,通过Dio网络请求工具,完成接口请求、数据解析、数据渲染的全流程实现,让轮播图展示真实的业务海报内容。

一、前置准备

获取轮播图API数据前,需要完成3个核心前置操作:安装网络请求工具Dio、定义相关常量(基础地址、接口地址等)、封装网络请求工具,为后续接口调用做好铺垫。以下是详细步骤和说明,每一步都需严格执行,避免后续出现请求失败、数据解析异常等问题。

1.1 前置准备完整步骤

按以下顺序执行前置操作,确保每一步都落地完成,为API数据获取打好基础:

  1. 安装Dio网络请求插件:用于发送HTTP请求,获取API接口数据(Flutter中最常用的网络请求工具);
  2. 定义常量数据:统一管理基础接口地址、请求超时时间、业务状态码、轮播图接口地址,便于后续维护和修改;
  3. 封装网络请求工具:对Dio进行二次封装,配置基础地址、超时时间、拦截器,简化后续接口调用流程;
  4. 请求工具进一步解构:处理HTTP状态码和业务状态码,统一拦截异常请求,返回真实可用的数据;
  5. 类工厂转化动态类型:将接口返回的JSON动态数据,转化为我们定义的BannerItem实体类对象,便于数据渲染;
  6. 封装请求API调用工厂函数:单独封装轮播图接口调用方法,分离业务逻辑和请求逻辑,提升代码可读性和可维护性;
  7. 初始化数据更新状态:在首页初始化时调用API请求方法,获取数据后更新页面状态,实现轮播图数据渲染。

1.2 接口请求成功示例

本次开发使用的真实轮播图API接口信息如下,可提前在浏览器或接口测试工具(如Postman)中测试,确认接口可正常返回数据,避免因接口问题导致开发受阻:

1.3 Dio插件安装方法

Dio是Flutter中功能强大的网络请求插件,支持GET、POST等多种请求方式,拦截器、超时设置等常用功能,安装步骤如下:

  1. 打开项目根目录的终端(或Android Studio底部的Terminal面板);

  2. 输入以下安装命令,按下回车,等待插件下载完成:

    bash 复制代码
    flutter pub add dio​
  3. 验证安装成功:命令执行完成后,终端会显示"added dio: ^x.x.x"(版本号可能不同),同时项目的pubspec.yaml文件中会自动新增dio的依赖配置,无需手动修改。

二、轮播图API请求实现

完成前置准备和Dio安装后,我们开始分步实现轮播图API数据获取,按"定义常量 → 封装请求工具 → 数据类型转换 → 封装API → 页面调用渲染"的顺序执行。

2.1 编写常量类(统一管理配置)

核心目的:将接口基础地址、超时时间、业务状态码、接口地址等常量,统一放在一个文件中管理,后续若需修改(如切换接口环境),只需修改该文件,无需修改所有调用处,提升代码可维护性。

操作步骤:lib目录下的constants文件夹,在该文件夹下新建index.dart文件,编写如下代码:

Dart 复制代码
//全局状态
class GlobalConstants {
  //基础地址
  static const String BASE_URL = "https://meikou-api.itheima.net/";
 
  //超时时间
  static const int TIME_OUT = 10;
 
  //成功状态
  static const String SUCCESS_CODE = "1";
}
 
//存放请求地址接口的常量
class HttpConstants{
  //轮播图接口
  static const String BANNER_LIST = "/home/banner";
}
 

2.2 封装Dio网络请求工具

核心目的:对Dio进行二次封装,配置基础地址、超时时间、拦截器,统一处理请求异常、响应结果,简化后续接口调用流程(无需每次调用接口都配置基础地址、超时时间)。

操作步骤:在lib/utils文件夹下新建DioRequest.dart文件,编写如下代码(完整封装,可直接复制使用):

Dart 复制代码
//基于Dio进行二次封装
import 'package:dio/dio.dart';
import 'package:xiuhu_mall/constants/index.dart';

class DioRequest {
  final _dio = Dio(); //dio请求对象

  //基础地址拦截器
  DioRequest() {
    _dio.options
      ..baseUrl = GlobalConstants.BASE_URL
      ..connectTimeout = Duration(seconds: GlobalConstants.TIME_OUT)
      ..sendTimeout = Duration(seconds: GlobalConstants.TIME_OUT)
      ..receiveTimeout = Duration(seconds: GlobalConstants.TIME_OUT);
    //拦截器-三个
    _addInterceptor();
  }

  // 添加拦截器
  void _addInterceptor() {
    _dio.interceptors.add(InterceptorsWrapper(onRequest: (request, handler) {
      handler.next(request);
    }, onResponse: (response, handler) {
      //http状态码 200 300
      if (response.statusCode! >= 200 && response.statusCode! < 300) {
        handler.next(response);
        return;
      }
      handler.reject(DioException(requestOptions: response.requestOptions));
    }, onError: (error, handler) {
      handler.reject(error);
    }));
  }

  //GET请求方法
  Future<dynamic> get(String url, {Map<String, dynamic>? params}) async {
    try {
      final response = await _dio.get(url, queryParameters: params);
      return _handleResponse(response);
    } catch (e) {
      rethrow;
    }
  }

  //进一步处理返回结果的函数
  Future<dynamic> _handleResponse(Response<dynamic> task) async {
    try {
      Response<dynamic> res = await task;
      final data = res.data as Map<String, dynamic>; //data才是我们真实的接口返回的数据
      if (data["code"] == GlobalConstants.SUCCESS_CODE) {
        //如果等于1 才认定http状态和业务状态均为正常 就可以正常的放行通过
        return data["result"]; //只要result结果
      }
      //抛出异常
      throw Exception(data["message"] ?? "加载数据异常");
    } catch (e) {
      throw Exception(e);
    }
  }
}

// 单例对象
final dioRequest = DioRequest();

//dio请求工具发出请求->返回的数据通过Response<dynamic>.data
//把所有的接口的data解放出来 拿到真正的数据 要判断业务状态码是不是等于1

封装逻辑说明:

  1. 单例对象:final dioRequest = DioRequest(); 确保全局只有一个Dio请求对象,避免重复创建导致的性能损耗;
  2. 拦截器作用:请求拦截可添加token、请求头;响应拦截统一处理HTTP状态码;错误拦截统一捕获请求异常;
  3. _handleResponse方法:核心是区分"网络层面成功"和"业务层面成功",只有HTTP状态码200~299且业务状态码=1,才返回真实数据;
  4. 异常处理:所有异常(请求超时、网络错误、业务异常、解析异常)都被捕获并抛出,让调用处统一处理(如提示用户)。

2.3 转换动态类型(JSON转实体类)

核心目的:接口返回的是JSON动态数据(Map类型),而我们轮播图组件需要的是BannerItem实体类对象的列表,因此需要通过工厂函数,将JSON数据转化为BannerItem对象,便于数据渲染和类型安全。

操作步骤:修改lib/viewmodels/home.dart文件(原有BannerItem类),添加工厂函数formJSON,完整代码如下:

Dart 复制代码
class BannerItem {
  String id;
  String imgUrl;
 
  BannerItem({required this.id, required this.imgUrl});
 
  //扩展一个工厂函数 一般用factory来声明 一般用来创建实例对象
  factory BannerItem.formJSON(Map<String, dynamic> json) {
    //必须返回一个BannerItem对象
    return BannerItem(id: json["id"] ?? "", imgUrl: json["imgUrl"] ?? "");
  }
}
 
//每一个轮播图具体类型
 
//flutter必须强制转换,没有隐式转化

2.4 封装API调用函数

核心目的:单独封装轮播图接口调用方法,将"网络请求"与"页面渲染"分离,提升代码可读性和可维护性,后续若需修改接口调用逻辑,只需修改该方法,无需修改页面代码。

操作步骤:在lib/api目录下新建home.dart文件,编写如下代码:

Dart 复制代码
//封装一个api 目的是返回业务侧要的数据结构
import 'package:xiuhu_mall/constants/index.dart';
import 'package:xiuhu_mall/utils/DioRequest.dart';
import 'package:xiuhu_mall/viewmodels/home.dart';

Future<List<BannerItem>> getBannerListAPI() async {
  //返回请求
  final tt =
  ((await dioRequest.get(HttpConstants.BANNER_LIST)) as List).map((item) {
    return BannerItem.formJSON(item as Map<String, dynamic>);
  }).toList();
  return tt;
}

2.5 页面调用API,渲染数据

核心目的:在首页初始化时,调用封装好的getBannerListAPI()方法,获取真实的轮播图API数据,更新页面状态,将数据传递给轮播图组件(HmSlider),实现真实数据渲染。

操作步骤:修改lib/pages/Home/index.dart文件,删除本地模拟数据,新增API调用逻辑,完整代码如下:

Dart 复制代码
import 'package:flutter/cupertino.dart';
import 'package:xiuhu_mall/api/home.dart';
import 'package:xiuhu_mall/components/Home/HmCategory.dart';
import 'package:xiuhu_mall/components/Home/HmHot.dart';
import 'package:xiuhu_mall/components/Home/HmMoreList.dart';
import 'package:xiuhu_mall/components/Home/HmSlider.dart';
import 'package:xiuhu_mall/components/Home/HmSuggestion.dart';
import 'package:xiuhu_mall/viewmodels/home.dart';

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

  @override
  State<HomeView> createState() => _HomeViewState();
}

class _HomeViewState extends State<HomeView> {
  List<BannerItem> _bannerList = [
    // BannerItem(
    //   id: "1",
    //   imgUrl: "https://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/meituan/1.jpg",
    // ),
    // BannerItem(
    //   id: "2",
    //   imgUrl: "https://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/meituan/2.png",
    // ),
    // BannerItem(
    //   id: "3",
    //   imgUrl: "https://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/meituan/3.jpg",
    // ),
  ];

  //获取滚动容器的内容

  List<Widget> _getScrollChildren() {
    return [
      //包裹普通widget的sliver家族的组件内容
      SliverToBoxAdapter(child: HmSlider(bannerList: _bannerList)), //轮播图组件
      //放置分类组件
      SliverToBoxAdapter(child: SizedBox(height: 10)),
      //SliverGrid SliverList指南纵向排列
      SliverToBoxAdapter(child: HmCategory()), //分类组件
      SliverToBoxAdapter(child: SizedBox(height: 10)),
      SliverToBoxAdapter(child: HmSuggestion()), //推荐组件
      SliverToBoxAdapter(child: SizedBox(height: 10)),

      //Flex和Expanded配合起来可以均分比例
      SliverToBoxAdapter(
        child: Padding(
            padding: EdgeInsets.symmetric(horizontal: 10),
            child: Flex(
              direction: Axis.horizontal,
              children: [
                Expanded(child: HmHot()),
                SizedBox(
                  width: 10,
                ),
                Expanded(child: HmHot()),
              ],
            )),
      ),
      SliverToBoxAdapter(child: SizedBox(height: 10)),
      HmMorelist(), //无限滚动列表
    ];
  }

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _getBannederList();
  }

  void _getBannederList() async {
    _bannerList = await getBannerListAPI();
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    //CustomScrollview要求:必须是sliver家族的内容
    return CustomScrollView(slivers: _getScrollChildren());
  }
}

核心修改点说明:

  1. 删除本地模拟数据:将原有_bannerList中的本地模拟数据删除,初始化为空列表;
  2. initState中调用API:initState是页面初始化生命周期方法,在此处调用_getBannerList(),确保页面加载时就获取轮播图数据;
  3. 异步请求处理:_getBannerList()方法添加async/await,处理API异步请求(API请求是耗时操作,需等待结果返回);
  4. 异常处理:添加try/catch捕获异常,避免请求失败导致页面崩溃,同时可设置默认数据,提升用户体验;
  5. setState刷新页面:获取到数据后,通过setState更新_bannerList,触发页面重新渲染,轮播图组件将展示真实API数据。

2.6 查看API请求效果:

真实API数据渲染的轮播图效果:

三、提交代码

完成轮播图API数据获取的所有功能开发、测试后,执行以下Git命令,将代码提交到远程仓库,保存当前开发进度

bash 复制代码
git add .
git commit -m "完成获取轮播图数据"
git push

四、总结

本文衔接上一节轮播图细节优化内容,实现了电商App首页「轮播图API数据获取」的开发,核心总结如下:

  1. 核心流程:本次开发的核心流程为「安装Dio → 定义常量 → 封装Dio请求 → JSON转实体类 → 封装API → 页面调用API → 数据渲染」;
  2. 核心知识点:Dio插件的安装与封装、拦截器的使用、async/await异步请求处理、JSON与实体类的转换、页面生命周期(initState)的使用、异常处理与默认数据设置;
  3. 代码规范:通过常量类统一管理配置、通过API封装类分离业务逻辑、通过实体类保证类型安全,提升了代码的可读性、可维护性和可复用性;

注意事项:

  • 确保Dio插件安装成功,且依赖配置正确;
  • 接口地址拼接正确;
  • 工厂函数字段与接口返回字段一致,且设置默认值,避免空指针异常;
  • 所有异步请求必须添加异常处理,避免页面崩溃;
  • 获取数据后必须调用setState,否则页面无法刷新渲染数据。

功能延伸:

  • 添加加载状态:请求数据时显示加载动画,请求完成后隐藏;
  • 添加错误提示:请求失败时,显示Toast提示用户"加载轮播图失败";
  • 添加缓存机制:将获取到的轮播图数据缓存到本地,下次打开页面优先加载缓存数据,提升加载速度;
  • 扩展其他接口:按照本次封装流程,可快速实现首页分类、推荐等其他业务接口的数据获取。

至此,轮播图获取真实的API数据功能已完善。

相关推荐
程序员Ctrl喵18 小时前
异步编程:Event Loop 与 Isolate 的深层博弈
开发语言·flutter
前端不太难20 小时前
Flutter 如何设计可长期维护的模块边界?
flutter
小蜜蜂嗡嗡21 小时前
flutter列表中实现置顶动画
flutter
始持21 小时前
第十二讲 风格与主题统一
前端·flutter
始持21 小时前
第十一讲 界面导航与路由管理
flutter·vibecoding
始持21 小时前
第十三讲 异步操作与异步构建
前端·flutter
新镜1 天前
【Flutter】 视频视频源横向、竖向问题
flutter
黄林晴1 天前
Compose Multiplatform 1.10 发布:统一 Preview、Navigation 3、Hot Reload 三箭齐发
android·flutter
Swift社区1 天前
Flutter 应该按功能拆,还是按技术层拆?
flutter
肠胃炎1 天前
树形选择器组件封装
前端·flutter