flutter网络请求的规范

Posted on 2025-07-03 15:08  lachesism  阅读(25)  评论(0)    收藏  举报

GET请求(无请求参数)

presenter.requestNetwork(
  bodyLoading: true,
  adapter: XXXAdapter().set(
    method: Method.get,
    successBaseCallback: (data) {
      updateUI(() {
        _dataBean = data;
      });
    },
    failCallback: (code, message) {
      ToastUtils.show(message);
    }
  )
);

 GET请求(有请求参数)

presenter.requestNetwork(
  bodyLoading: true,
  adapter: XXXAdapter(
    id: itemId,
    pageNum: 1,
    pageSize: 20
  ).set(
    method: Method.get,
    successBaseCallback: (data) {
      updateUI(() {
        _dataBean = data;
      });
    },
    failCallback: (code, message) {
      ToastUtils.show(message);
    }
  )
);

 POST请求(无请求参数)

presenter.requestNetwork(
  bodyLoading: true,
  adapter: XXXAdapter().set(
    method: Method.post,
    successBaseCallback: (data) {
      updateUI(() {
        _dataBean = data;
      });
      ToastUtils.show('操作成功');
    },
    failCallback: (code, message) {
      ToastUtils.show(message);
    }
  )
);

 POST请求(有请求参数)

presenter.requestNetwork(
  bodyLoading: true,
  adapter: XXXAdapter(
    userId: currentUserId,
    content: textContent,
    imageList: selectedImages
  ).set(
    method: Method.post,
    successBaseCallback: (data) {
      updateUI(() {
        _isSubmitted = true;
      });
      ToastUtils.show('提交成功');
      Navigator.pop(context);
    },
    failCallback: (code, message) {
      ToastUtils.show(message.isEmpty ? '提交失败' : message);
    }
  )
);

 

  • Android原生Adapter(如RecyclerView.Adapter)主要用于UI组件和数据之间的绑定
  • 这个项目中的Adapter是网络层的概念,用于API请求和响应处理
 

完整示范:实现商品推荐列表功能

步骤1:创建数据模型类(Bean)

首先,创建一个表示商品推荐数据的Bean类:
// 文件路径:lib/pages/home/bean/recommend_product_bean.dart

class RecommendProductBean {
  final int total;
  final List<RecommendProductItemBean> list;

  RecommendProductBean({
    required this.total,
    required this.list,
  });

  factory RecommendProductBean.fromJson(Map<String, dynamic> json) {
    List<RecommendProductItemBean> productList = [];
    if (json['list'] != null) {
      for (var item in json['list']) {
        productList.add(RecommendProductItemBean.fromJson(item));
      }
    }
    
    return RecommendProductBean(
      total: json['total'] ?? 0,
      list: productList,
    );
  }
}

class RecommendProductItemBean {
  final String id;
  final String title;
  final String imageUrl;
  final double price;
  final int sales;

  RecommendProductItemBean({
    required this.id,
    required this.title,
    required this.imageUrl,
    required this.price,
    required this.sales,
  });

  factory RecommendProductItemBean.fromJson(Map<String, dynamic> json) {
    return RecommendProductItemBean(
      id: json['id'] ?? '',
      title: json['title'] ?? '',
      imageUrl: json['image_url'] ?? '',
      price: (json['price'] ?? 0).toDouble(),
      sales: json['sales'] ?? 0,
    );
  }
}

步骤2:创建网络请求适配器(Adapter)

接下来,创建一个处理推荐商品API请求的Adapter

// 文件路径:lib/pages/home/adapter/recommend_product_adapter.dart

import 'package:jiling/net/adapter/api_adapter.dart';
import 'package:jiling/pages/home/bean/recommend_product_bean.dart';

class RecommendProductAdapter extends ApiAdapter<RecommendProductBean> {
  final int pageNum;
  final int pageSize;
  final String? categoryId; // 可选参数,按分类筛选

  RecommendProductAdapter({
    required this.pageNum,
    required this.pageSize,
    this.categoryId,
  });

  @override
  String get path => '/api/product/recommend'; // API路径

  @override
  Map<String, dynamic> get params {
    // 构建请求参数
    Map<String, dynamic> requestParams = {
      'page_num': pageNum,
      'page_size': pageSize,
    };
    
    // 如果有分类ID,添加到参数中
    if (categoryId != null && categoryId!.isNotEmpty) {
      requestParams['category_id'] = categoryId;
    }
    
    return requestParams;
  }

  @override
  RecommendProductBean convert(Map<String, dynamic> data) {
    // 将JSON数据转换为Bean对象
    return RecommendProductBean.fromJson(data);
  }
}

 

步骤3:在页面中使用网络请求

现在,创建一个页面组件来展示推荐商品列表:

// 文件路径:lib/pages/home/widgets/recommend_product_widget.dart

import 'package:flutter/material.dart';
import 'package:jiling/base/base_view_state.dart';
import 'package:jiling/net/dio_net.dart';
import 'package:jiling/pages/home/adapter/recommend_product_adapter.dart';
import 'package:jiling/pages/home/bean/recommend_product_bean.dart';
import 'package:jiling/res/colors.dart';
import 'package:jiling/res/font_sizes.dart';
import 'package:jiling/utils/common_utils.dart';
import 'package:jiling/widgets/empty.dart';
import 'package:dio/dio.dart';

class RecommendProductWidget extends StatefulWidget {
  final String? categoryId;
  
  const RecommendProductWidget({Key? key, this.categoryId}) : super(key: key);

  @override
  _RecommendProductWidgetState createState() => _RecommendProductWidgetState();
}

class _RecommendProductWidgetState extends BaseViewState<RecommendProductWidget> {
  List<RecommendProductItemBean> _productList = [];
  bool _isLoading = false;
  String? _errorMessage;
  int _pageNum = 1;
  final int _pageSize = 10;
  CancelToken? _cancelToken;

  @override
  void initState() {
    super.initState();
    _cancelToken = CancelToken(); // 创建取消令牌
    _loadRecommendProducts();
  }

  @override
  void dispose() {
    // 组件销毁时取消请求
    _cancelToken?.cancel("组件销毁,取消请求");
    _cancelToken = null;
    super.dispose();
  }

  // 加载推荐商品列表
  Future<void> _loadRecommendProducts() async {
    // 第一页加载时显示加载状态
    if (_pageNum == 1) {
      updateUI(() {
        _isLoading = true;
        _errorMessage = null;
      });
    }

    CommonUtils.ilog('请求推荐商品列表,页码: $_pageNum, 每页: $_pageSize');
    
    // 使用presenter.requestNetwork发起请求
    presenter.requestNetwork(
      bodyLoading: false, // 不使用全屏加载
      cancelToken: _cancelToken, // 传入取消令牌
      adapter: RecommendProductAdapter(
        pageNum: _pageNum,
        pageSize: _pageSize,
        categoryId: widget.categoryId,
      ).set(
        method: Method.get, // 使用GET请求
        successBaseCallback: (data) {
          // 请求成功回调
          CommonUtils.ilog('推荐商品数据加载成功: 共${data.total}个商品');
          
          // 使用updateUI更新状态
          updateUI(() {
            if (_pageNum == 1) {
              // 第一页,直接替换数据
              _productList = data.list;
            } else {
              // 不是第一页,追加数据
              _productList.addAll(data.list);
            }
            _isLoading = false;
            _errorMessage = null;
          });
        },
        failCallback: (code, message) {
          // 请求失败回调
          CommonUtils.ilog('推荐商品加载失败: 错误码=$code, 错误信息=$message');
          
          // 更新状态
          updateUI(() {
            _isLoading = false;
            _errorMessage = message;
          });
        }
      )
    );
  }

  // 加载更多数据
  void _loadMore() {
    _pageNum++;
    _loadRecommendProducts();
  }

  // 刷新数据
  Future<void> _refresh() async {
    _pageNum = 1;
    await _loadRecommendProducts();
    return Future.value();
  }

  @override
  Widget build(BuildContext context) {
    final double scale = CommonUtils.getScale(context);
    
    // 显示加载状态
    if (_isLoading && _productList.isEmpty) {
      return Center(
        child: CircularProgressIndicator(
          valueColor: AlwaysStoppedAnimation<Color>(Colours.J0AB8A6),
        ),
      );
    }
    
    // 显示错误信息
    if (_errorMessage != null && _productList.isEmpty) {
      return Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              '加载失败: $_errorMessage',
              style: TextStyle(
                color: Colours.J262626,
                fontSize: FontSizes.f14,
              ),
            ),
            SizedBox(height: 16),
            TextButton(
              onPressed: _refresh,
              child: Text('重试'),
            ),
          ],
        ),
      );
    }
    
    // 显示空状态
    if (_productList.isEmpty) {
      return Center(
        child: Empty(text: '暂无推荐商品'),
      );
    }
    
    // 显示商品列表
    return RefreshIndicator(
      onRefresh: _refresh,
      child: ListView.builder(
        itemCount: _productList.length + 1, // +1 用于底部加载更多
        itemBuilder: (context, index) {
          // 到达列表底部,显示加载更多
          if (index == _productList.length) {
            return Padding(
              padding: EdgeInsets.all(16.0 * scale),
              child: Center(
                child: Text(
                  '上拉加载更多',
                  style: TextStyle(
                    color: Colours.J999999,
                    fontSize: FontSizes.f12,
                  ),
                ),
              ),
            );
          }
          
          // 显示商品项
          final product = _productList[index];
          return _buildProductItem(product, scale);
        },
      ),
    );
  }
  
  // 构建商品项UI
  Widget _buildProductItem(RecommendProductItemBean product, double scale) {
    return GestureDetector(
      onTap: () {
        // 跳转到商品详情页
        Navigators.productDetailPage(
          context,
          productId: product.id,
          imageUrl: product.imageUrl,
        );
      },
      child: Container(
        margin: EdgeInsets.symmetric(
          horizontal: 16.0 * scale,
          vertical: 8.0 * scale,
        ),
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(8.0 * scale),
          boxShadow: [
            BoxShadow(
              color: Colors.black.withOpacity(0.05),
              blurRadius: 4.0 * scale,
              offset: Offset(0, 2.0 * scale),
            ),
          ],
        ),
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 商品图片
            ClipRRect(
              borderRadius: BorderRadius.only(
                topLeft: Radius.circular(8.0 * scale),
                bottomLeft: Radius.circular(8.0 * scale),
              ),
              child: ImageWidget(
                url: product.imageUrl,
                width: 100.0 * scale,
                height: 100.0 * scale,
                fit: BoxFit.cover,
              ),
            ),
            
            // 商品信息
            Expanded(
              child: Padding(
                padding: EdgeInsets.all(12.0 * scale),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    // 商品标题
                    Text(
                      product.title,
                      style: TextStyle(
                        fontSize: FontSizes.f14,
                        color: Colours.J262626,
                        fontWeight: FontWeight.bold,
                      ),
                      maxLines: 2,
                      overflow: TextOverflow.ellipsis,
                    ),
                    
                    SizedBox(height: 8.0 * scale),
                    
                    // 商品价格
                    Text(
                      '¥${product.price.toStringAsFixed(2)}',
                      style: TextStyle(
                        fontSize: FontSizes.f16,
                        color: Colours.JFF3B30,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    
                    SizedBox(height: 4.0 * scale),
                    
                    // 销量
                    Text(
                      '已售${product.sales}件',
                      style: TextStyle(
                        fontSize: FontSizes.f12,
                        color: Colours.J999999,
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

 

步骤4:在主页面中使用该组件

最后,在主页面中使用这个组件:

// 文件路径:lib/pages/home/home_page.dart (部分代码)

import 'package:flutter/material.dart';
import 'package:jiling/pages/home/widgets/recommend_product_widget.dart';

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('首页')),
      body: Column(
        children: [
          // 其他UI组件...
          
          // 标题
          Padding(
            padding: EdgeInsets.all(16.0),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text(
                  '推荐商品',
                  style: TextStyle(
                    fontSize: 18.0,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                GestureDetector(
                  onTap: () {
                    // 查看更多
                    Navigators.productListPage(
                      context,
                      initialType: ProductListType.recommend,
                    );
                  },
                  child: Text(
                    '查看更多',
                    style: TextStyle(
                      color: Colors.blue,
                      fontSize: 14.0,
                    ),
                  ),
                ),
              ],
            ),
          ),
          
          // 推荐商品列表
          Expanded(
            child: RecommendProductWidget(),
          ),
        ],
      ),
    );
  }
}

 

总结:网络请求完整流程

  1. 定义数据模型(Bean)
  • 创建与API响应对应的数据模型类
  • 实现fromJson方法,将JSON转换为对象
  1. 创建适配器(Adapter)
  • 继承ApiAdapter<T>,T为返回数据类型
  • 定义API路径、请求参数
  • 实现convert方法,处理数据转换
  1. 在页面中使用
  • 创建CancelToken用于管理请求生命周期
  • 使用presenter.requestNetwork发起请求
  • successBaseCallback中处理成功响应
  • failCallback中处理错误
  • 使用updateUI而非setState更新状态
  • dispose中取消未完成的请求
  1. 处理加载状态和错误
  • 显示加载指示器
  • 处理错误信息
  • 实现下拉刷新和加载更多