[Flutter-3]HelloVicent综合页——处理网络请求/Json模型的自动生成/实现Banner轮播

272 阅读2分钟

今天是2023-12-22,项目第三天

参考文章

正文

开始造综合页的轮播图和下面的文章列表;

抓包到banner接口为

www.oschina.net/action/apiv…

网络请求封装

先行实现项目网络请求的封装,参考以下文章: Flutter 封装网络请求库 Dio

pubspec.yaml 导入三方库,并执行 pub get

dio: ^5.4.0
flutter_easyloading: ^3.0.5
common_utils: ^2.1.0

注意⚠️:EasyLoading 需要在 MaterialApp 初始化,否则会报错,如下;

.........
return MaterialApp(
  theme: ThemeData(
      primaryColor: themeColor,
      //去除TabBar下划线 plan1
      useMaterial3: false,
      //去除TabBar下划线 plan2
      // tabBarTheme: const TabBarTheme(dividerColor: Colors.transparent)
      ),
  builder: EasyLoading.init(),
  home: Scaffold(
 .........

Request和Api的实现:

request.dart

import 'dart:convert';
import 'package:common_utils/common_utils.dart';
import 'package:dio/dio.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';

class Request {
  // 配置 Dio 实例
  static final BaseOptions _options = BaseOptions(
    baseUrl: "https://www.oschina.net/",
    connectTimeout: const Duration(milliseconds: 5000),
    receiveTimeout: const Duration(milliseconds: 3000),
  );

  // 创建 Dio 实例
  static final Dio _dio = Dio(_options);

  // _request 是核心函数,所有的请求都会走这里
  static Future<T> _request<T>(String path,
      {String method = 'GET', Map? params,data}) async {
    // restful 请求处理
    if (params != null) {
      params.forEach((key, value) {
        if (path.contains(key)) {
          path = path.replaceAll(":$key", value.toString());
        }
      });
    }
    LogUtil.v(data, tag: 'send data is:');

    try {
      Response response = await _dio.request(
          path, data: data, options: Options(method: method));
      if (response.statusCode == 200 || response.statusCode == 201) {
        try {
          if (response.data['code'] != 1) {
            LogUtil.v(
                response.data['code'], tag: 'server error, status code is:');
            EasyLoading.showInfo(
                "服务器错误,状态码为:${response.data['code']}");
            return Future.error(response.data['message']);
          } else {
            LogUtil.v(response.data, tag: 'response data:');
            if (response.data is Map) {
              return response.data;
            } else {
              return json.decode(response.data.toString());
            }
          }
        } catch (e) {
          LogUtil.v(e, tag: 'decode response data error');
          return Future.error('decode response data error');
        }
      } else {
        LogUtil.v(response.statusCode, tag: 'HTTP error, status code:');
        EasyLoading.showInfo('HTTP错误,状态码为:${response.statusCode!}');
        _handleHttpError(response.statusCode!);
        return Future.error('HTTP Error');
      }
    } on DioException catch (e, s) {

      LogUtil.v(_dioError(e), tag: 'request error');
      EasyLoading.showInfo(_dioError(e));
      return Future.error(_dioError(e));

    } catch (e, s) {
      LogUtil.v(e, tag: 'unknown error');
      return Future.error('unknown error');
    }
  }

  // 处理 Dio 异常
  static String _dioError(DioException error) {
    switch (error.type) {
      case DioExceptionType.connectionTimeout:
        return "网络连接超时,请检查网络设置";
        //不会执行,可以移除的代码,实际需要加break;
        // Dead code. (Documentation)
        // Try removing the code, or fixing the code before it so that it can be reached.
        break;
      case DioExceptionType.receiveTimeout:
        return "服务器异常,请稍后重试!";
      case DioExceptionType.sendTimeout:
        return "网络连接超时,请检查网络设置";
      case DioExceptionType.badResponse:
        return "服务器异常,请稍后重试!";
      case DioExceptionType.cancel:
        return "请求已被取消,请重新请求";
      case DioExceptionType.unknown:
        return "网络异常,请稍后重试!";
      default:
        return "Dio异常";
    }
  }

  // 处理 Http 错误码
  static void _handleHttpError(int errorCode) {
    String message;
    switch (errorCode) {
      case 400:
        message = '请求语法错误';
        break;
      case 401:
        message = '未授权,请登录';
        break;
      case 403:
        message = '拒绝访问';
        break;
      case 404:
        message = '请求出错';
        break;
      case 408:
        message = '请求超时';
        break;
      case 500:
        message = '服务器异常';
        break;
      case 501:
        message = '服务未实现';
        break;
      case 502:
        message = '网关错误';
        break;
      case 503:
        message = '服务不可用';
        break;
      case 504:
        message = '网关超时';
        break;
      case 505:
        message = 'HTTP版本不受支持';
        break;
      default:
        message = '请求失败,错误码:$errorCode';
    }
    EasyLoading.showError(message);
  }

  static Future<T> get<T>(String path,{Map? params}) {
    return _request(path,params: params);
  }

  static Future<T> post<T>(String path, { Map? params,data}) {
    return _request(path, method: 'post', params: params, data: data);
  }
}

api.dart

import 'request.dart';
class Api {

  //banner
  static summaryNewBanner(){
    return Request.get('/action/apiv2/banner?catalog=1');
  }
  static login(data){
    return Request.post('/login', data: data);
  }
}

Json转模型的方式

通过参考以下文章,选择了安装Android Studio 的插件 FlutterJsonBeanFactory 实现,自动生成Model;

Flutter-JSON转Model的四种便捷方案

并自动生成了Banner的模型类,点击去往 Gitee 查看 summary_new_banner_entity.dart 类;

轮播图的实现

导入三方类,来实现轮播效果,但是左下角需要添加标题,后续再看怎么实现;

flutter_swiper_plus: ^2.0.4

具体代码为

@override
Widget build(BuildContext context) {

  return Scaffold(
    body: SizedBox(
      height: 150.0,
      child: Padding(
        padding: const EdgeInsets.all(12.0),
        child: ClipRRect(
          borderRadius: BorderRadius.circular(8),
            child: Swiper(
                itemBuilder: (BuildContext context,int index){
                  return Image.network(_bannerList[index].img!,fit: BoxFit.cover,);
                },
              onTap: (index){

              },
              autoplay: true,
                itemCount: _bannerList.length,
                pagination: const SwiperPagination(
                  alignment: Alignment.bottomRight,
                ),
              ),
          ),
          ),
    ),
  );

效果图:

IMG_4991.PNG

整体代码实现

import "package:flutter/material.dart";
import 'package:flutter_swiper_plus/flutter_swiper_plus.dart';
import 'package:hello_oschina/http/api.dart';
import 'package:hello_oschina/models/summary_new_banner_entity.dart';
class SummaryNewPage extends StatefulWidget {
  const SummaryNewPage({super.key});

  @override
  State<SummaryNewPage> createState() => _SummaryNewPageState();
}

class _SummaryNewPageState extends State<SummaryNewPage> {

  List<SummaryNewBannerResultItems> _bannerList = [];

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

    getBannerData();
  }
  void getBannerData() async {
    var data = await Api.summaryNewBanner();
    SummaryNewBannerEntity entity = SummaryNewBannerEntity.fromJson(data);
    print(entity.result?.items);
    if (entity.result!.items!.isNotEmpty){
        setState(() {
          _bannerList = entity.result!.items!;
        });
    }
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      body: SizedBox(
        height: 150.0,
        child: Padding(
          padding: const EdgeInsets.all(12.0),
          child: ClipRRect(
            borderRadius: BorderRadius.circular(8),
              child: Swiper(
                  itemBuilder: (BuildContext context,int index){
                    return Image.network(_bannerList[index].img!,fit: BoxFit.cover,);
                  },
                onTap: (index){

                },
                autoplay: true,
                  itemCount: _bannerList.length,
                  pagination: const SwiperPagination(
                    alignment: Alignment.bottomRight,
                  ),
                ),
            ),
            ),
      ),
    );
  }
}

以上;