Flutter 异步加载数据并渲染列表

这里踩了一脚坑,写flutter的时候还是与js有差异的,这里主要涉及到几个知识点,看官需要对几部分内容先理解才容易处理,PS:其实现在我也还有点懵~

1. 网络请求,这里我分别使用了dio和http两个库

2. dart的json解析,不得不说,这个真神奇~

3 listView渲染及嵌套listView

4 FutureBuilder使用

吐槽:

坑1: dart对json的处理其实真的不方便,不能像js那样拿来就用,起初也找了一些json转换的插件,但是用着也都很不爽,一直有各种错误抛出来,最后决定还是自己写model解析json;

坑2: listView的嵌套主要注意一下子listView需要多设置两个属性就可以了,应该是为了解决滑动和计算子项高度的。

坑3:异步渲染还是使用FutureBuilder吧,这东西简单好用,最初我像js那样建一个全局变量,然后在http请求之后,使用setstate去赋值,看着逻辑很对,但是一直在第一次渲染的时候报空指针,我猜是还没有返回数据就渲染ui了,但是一直没解决掉,也可能是那个时候json解析还不太对,后来使用了这个FutureBuilder,从此没有烦恼了~

特别深奥的原理我还解释不清,只记录一下踩坑的点,大家多注意即可

下面是代码,欢迎指正,我分别给出我的dio请求库,和此处功能实现的代码

1 dio请求库

import 'package:dio/dio.dart';
import 'dart:async';


class DioUtil{

  /// global dio object
  static Dio dio;

  /// default options
  static const String API_PREFIX = 'http://10.0.3.2:8086/ylr-admin';
  static const int CONNECT_TIMEOUT = 10000;
  static const int RECEIVE_TIMEOUT = 3000;

  /// http request methods
  static const String GET = 'get';
  static const String POST = 'post';
  static const String PUT = 'put';
  static const String PATCH = 'patch';
  static const String DELETE = 'delete';

  /// request method
  static Future<Map> request(String url, {data, method}) async{

    data = data ?? {};
    method = method ?? GET;

    /// restful 请求处理
    /// /gysw/search/hist/:user_id        user_id=27
    /// 最终生成 url 为     /gysw/search/hist/27
    data.forEach((key, value){
      if(url.indexOf(key) != -1){
        url = url.replaceAll(':$key', value.toString());
      }
    });

    /// 打印请求相关信息:请求地址、请求方式、请求参数
//    print('请求地址:【' + method + '  ' + url + '】');
//    print('请求参数:' + data.toString());

    Dio dio = createInstance();
    var result;

    try {
      Response response = await dio.request(url, data: data, options: new Options(method: method));

      result = response.data;

      /// 打印响应相关信息
//      print('响应数据:' + response.toString());
    } on DioError catch (e) {
      /// 打印请求失败相关信息
//      print('请求出错:' + e.toString());
    }

    return result;

  }

  /// 创建 dio 实例对象
  static Dio createInstance () {
    if (dio == null) {
      /// 全局属性:请求前缀、连接超时时间、响应超时时间
      BaseOptions options = BaseOptions(
        baseUrl: API_PREFIX,
        connectTimeout: CONNECT_TIMEOUT,
        receiveTimeout: RECEIVE_TIMEOUT,
      );

      dio = new Dio(options);
    }

    return dio;
  }

  /// 清空 dio 对象
  static clear () {
    dio = null;
  }


}

我也是参考其他人的写法,拿来用的,这里说明一下API_PREFIX ,我用的genymotion模拟器 ,这个东西和本地交互的ip是10.0.3.2

2 功能实现

import 'dart:convert';

import 'package:date_format/date_format.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:ylr_app/base/base_detail.dart';
import 'package:ylr_app/constants/constants.dart';
import 'package:ylr_app/utils/dio_util.dart';
import 'package:ylr_app/widget/home_news_more.dart';
import 'package:http/http.dart' as http;

/// 资讯
class HomeNews extends StatefulWidget {
  @override
  _HomeNewsState createState() => _HomeNewsState();
}

class _HomeNewsState extends State<HomeNews> {

  // http
  Future<NewsModel> fetchPost() async {
    final response =
        await http.get(Constants.BASE_URL + '/api/article/getArticleList');
    Utf8Decoder utf8decoder = Utf8Decoder(); //fix 中文乱码
    var result = json.decode(utf8decoder.convert(response.bodyBytes));
    return NewsModel.fromJson(result);
  }

  // dio
  Future<NewsModel> _getDataList() async {
    var result = await DioUtil.request('/api/article/getArticleList',
        method: DioUtil.GET);

    return NewsModel.fromJson(result);
  }

  Widget _listItemBuilder(item) {
    return GestureDetector(
      child: Container(
        margin: EdgeInsets.only(
          top: 10,
        ),
        height: ScreenUtil().setHeight(60),
        child: Flex(
          direction: Axis.horizontal,
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Expanded(
              flex: 1,
              child: Text('${item.title}',overflow: TextOverflow.ellipsis,),
            ),
            Container(
              alignment: Alignment.centerRight,
              width: ScreenUtil().setWidth(260),
              child: item.updateTime != null ?
              Text(formatDate(DateTime.parse('${item.updateTime}'), [yyyy, '-', mm, '-', dd],)):
              Text(formatDate(DateTime.parse('${item.createTime}'), [yyyy, '-', mm, '-', dd],)),
            ),
          ],
        ),
      ),
      onTap: () {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => BaseDetail(
              contentTitle: '${item.title}',
              isShowVideo: false,
              id: int.parse('${item.id}'),
            ),
          ),
        );
      },
    );
  }

  Widget _createListView(BuildContext context, AsyncSnapshot snapshot){
    if(snapshot.hasData){

      //数据处理
      var data = snapshot.data;
      List<NewsItemModel> listData = (data.result as List).cast();

      return ListView.builder(
        shrinkWrap: true,
        physics: NeverScrollableScrollPhysics(),
        itemBuilder: (context, index) {
          NewsItemModel item = listData[index];

          return _listItemBuilder(item);
        },
        itemCount: listData.length,
      );

    }
  }

  Widget _buildFuture(BuildContext context, AsyncSnapshot snapshot) {
    switch (snapshot.connectionState) {
      case ConnectionState.none:
        return Text('还没开始网络请求');
      case ConnectionState.active:
        return Text('ConnectionState.active');
      case ConnectionState.waiting:
        return Center(
          child: CircularProgressIndicator(),
        );
      case ConnectionState.done:
        if (snapshot.hasError) return Text('Error: ${snapshot.error}');
        return _createListView(context, snapshot);
      default:
        return null;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.only(bottom: 5),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(5),
        border: Border.all(
          color: Color(Constants.border),
        ),
      ),
      child: Padding(
        padding: EdgeInsets.all(10),
        child: Column(
          children: <Widget>[
            Container(
              decoration: BoxDecoration(
                image: DecorationImage(
                  image: AssetImage('assets/images/listbar.png'),
                  fit: BoxFit.fitWidth,
                ),
              ),
              child: Row(
                children: <Widget>[
                  Container(
                    width: ScreenUtil().setWidth(360),
                    margin: EdgeInsets.only(left: 10),
                    child: Text(
                      Constants.MODULE_HOME_NEWS,
                      style: TextStyle(
                        fontSize: Constants.font40,
                      ),
                    ),
                  ),
                  Expanded(
                    flex: 1,
                    child: GestureDetector(
                      child: Row(
                        mainAxisAlignment: MainAxisAlignment.end,
                        children: <Widget>[
                          Text(
                            Constants.MORE,
                            style: TextStyle(
                              fontSize: Constants.font30,
                            ),
                          ),
                          Icon(
                            Icons.arrow_forward_ios,
                            color: Colors.grey,
                            size: Constants.font36,
                          ),
                        ],
                      ),
                      onTap: () {
                        Navigator.push(
                          context,
                          MaterialPageRoute(
                            builder: (context) => HomeNewsMore(),
                          ),
                        );
                      },
                    ),
                  ),
                ],
              ),
            ),
            FutureBuilder(
              builder: _buildFuture,
              future: _getDataList(),
            ),
          ],
        ),
      ),
    );
  }
}

// dart model
class NewsModel {
  int code;
  int timestamp;
  String message;
  bool success;
  List<NewsItemModel> result;

  NewsModel(
      {this.code, this.timestamp, this.message, this.success, this.result});

  factory NewsModel.fromJson(Map<String, dynamic> json) {
    final originList = json['result'] as List;

    List<NewsItemModel> newsItemModelList =
        originList.map((value) => NewsItemModel.fromJson(value)).toList();

    return NewsModel(
        code: json['code'],
        timestamp: json['timestamp'],
        message: json['message'],
        success: json['success'],
        result: newsItemModelList);
  }
}

class NewsItemModel {
  int id;
  String title;
  String createTime;
  String updateTime;

  NewsItemModel({this.id, this.title, this.createTime, this.updateTime});

  factory NewsItemModel.fromJson(Map<String, dynamic> json) {
    return NewsItemModel(
        id: json['id'],
        title: json['title'],
        createTime: json['createTime'],
        updateTime: json['updateTime']);
  }
}

这里给出了两种http请求库的使用方法,以及它们分别如何与自定义的dart model转换,最终生成列表是使用listView做的,原本的想法,是用for循环数据List渲染UI,也是一直报各种问题,最终都使用flutter提供的组件实现了,我想应该是会有其他办法实现,但是我对dart的语法还是不够熟悉,现阶段只能这么做了,以后有机会再试试其他写法,有知道的朋友路过,欢迎指教,提供给我一下事例代码,我就瞟一眼~

 

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
要实现异步请求网络数据并显示中demo,我们可以使用Flutter框架的http插件和状态管理来完成。 首先,我们需要在pubspec.yaml文件中添http插件的依赖。 ``` dependencies: flutter: sdk: flutter http: ^0.13.0 ``` 然后,在需要显示中的页面中,创建一个有状态的组件。在该组件的状态中定义一个布尔值isLoading来表示数据是否正在中。 ```dart import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; class LoadingDemo extends StatefulWidget { @override _LoadingDemoState createState() => _LoadingDemoState(); } class _LoadingDemoState extends State<LoadingDemo> { bool isLoading = false; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Loading Demo'), ), body: Center( child: isLoading ? CircularProgressIndicator() : RaisedButton( child: Text('数据'), onPressed: () { fetchData(); }, ), ), ); } void fetchData() async { setState(() { isLoading = true; }); var response = await http.get('https://api.example.com/data'); if (response.statusCode == 200) { // 请求成功 var data = json.decode(response.body); // 处理数据 } else { // 请求失败 } setState(() { isLoading = false; }); } } ``` 在上述代码中,isLoading初始值为false,当点击数据按钮时,调用fetchData函数来请求数据。在请求数据时,将isLoading设置为true来显示动画,请求完成后再将其设置为false。 为了能够在Flutter中显示动画,我们使用了CircularProgressIndicator小部件,它会在屏幕中间显示一个圆形的进度指示器。 通过上述代码,我们可以实现一个简单的中demo,用于异步请求网络数据并显示状态。当点击数据按钮时,会显示动画,请求完成后隐藏动画。只需简单几步即可实现这样的功能。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值