16.第三方库导入与网络数据异步请求与展示

前言

在 iOS 开发中 我们经常使用 CocoaPods 等工具进行包管理。那么在 flutter 开发中又该如何进行呢?

一、 http

这是官方提供来一个网络库:http

点击复制

1.1 修改 pubspec.yaml 配置文件

1.2 更新依赖库

1.3 在使用的地方导入

import 'package:http/http.dart' as http;

二、 异步函数

2.1 简单汇总Service

简单汇总一下Service,便于后续使用。

import 'package:flutter/material.dart';

class ServiceURL {
  /// 会话列表
  static Uri conversationList = Uri.parse('xxxxxx');
}

2.2 发送请求

这里简单选择在 initState 中进行网络请求,调用移步方法

  @override
  void initState() {
    super.initState();
    _loadConversation();
  }

  /// 加载会话列表
  void _loadConversation() async {
    var response = await http.get(ServiceURL.conversationList);
    print(response.body);
  }

这里可以发现,和我们在 iOS 开发中的网络请求的调用形式有很大差别。iOS开发中我们都是通过回调的形式接收请求结果的。这里使用 await

三、 数据模型转换

接收到网络数据后我们自然需要进行数据模型的转换。

3.1 定义数据模型

import 'package:flutter/material.dart';

class ConversationData {
  final String nick;
  final String avatar;
  final String lastestMessage;

  ConversationData({
    required this.nick,
    required this.avatar,
    required this.lastestMessage,
  });

  factory ConversationData.formMap(Map map) {
    return ConversationData(
      nick: map['nick'],
      avatar: map['avatar'],
      lastestMessage: map['lastest_message'],
    );
  }
}

3.2 Json 转 Map

  List _conversations = [];
  bool _isLoading = false;

  /// 加载会话列表
  void _loadConversation() async {
    _isLoading = true;
    var response = await http.get(ServiceURL.conversationList);
    _isLoading = false;
    if (response.statusCode == 200) {
      final bodyMap = json.decode(response.body);
      final result = bodyMap['lists'].map((item) {
        return ConversationData.formMap(item);
      }).toList();

      setState(() {
        _conversations = result;
      });
    } else {
      throw Exception('请求失败:${response.statusCode}');
    }
  }

3.3 构建 Cell

  • Text

    • 样式设置

    • 缩略方式设置

    • 设置最大宽度

  • Container

    • 设置图片圆角

class ConversationCell extends StatelessWidget {
  ConversationCell({required this.conversation});

  final ConversationData conversation;

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 60,
      color: Colors.white,
      child: Stack(
        children: [
          Align(
            alignment: Alignment.center,
            child: Container(
              child: Row(
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  const SizedBox(
                    width: 12,
                  ),
                  // 圆角
                  Container(
                    width: 48,
                    height: 48,
                    decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(4),
                      image: DecorationImage(
                          // 网络图片
                        image: NetworkImage(conversation.avatar),
                      ),
                    ),
                  ),
                  const SizedBox(
                    width: 12,
                  ),
                  Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const SizedBox(height: 12),
                      Text(
                        conversation.nick,
                        maxLines: 1,
                        style: const TextStyle(
                          fontSize: 14,
                          fontWeight: FontWeight.w500,
                        ),
                      ),
                      Container(
                          // 限制宽度
                        width: UIConfig.screenWidth(context) * 0.7,
                        child: Text(
                          conversation.lastestMessage,
                          maxLines: 1,
                          // 缩略样式
                          overflow: TextOverflow.ellipsis,
                          style: const TextStyle(
                            fontSize: 12,
                            fontWeight: FontWeight.w400,
                          ),
                        ),
                      )
                    ],
                  ),
                ],
              ),
            ),
          ),
          // 分割线
          Align(
            alignment: Alignment.bottomCenter,
            child: Container(
              margin: const EdgeInsets.only(left: 70),
              height: 0.5,
              color: Colors.grey,
            ),
          ),
        ],
      ),
    );
  }
}

3.4 构建 ListView

  Widget _itemForRow(BuildContext context, int index) {
      return ConversationCell(conversation: _conversations[index]);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(...),
      body: Container(
        color: Colors.yellow,
        child: _isLoading
            ? Center(
                child: Text('Loading'),
              )
            : ListView.builder(
                itemBuilder: _itemForRow,
                itemCount: _conversations.length,
              ),
      ),
    );
  }

3.5 效果

四、 Future & FutureBuilder

除了按照上面这种我们在 iOS 开发中一样常规的思路进行请求和展示外,再介绍另一种可以处理这种场景。

4.1 请求方法修改

之前的加载方法:

  /// 加载会话列表
  void _loadConversation() async {
    var response = await http.get(ServiceURL.conversationList);
    if (response.statusCode == 200) {
      final bodyMap = json.decode(response.body);
      
      _conversations = bodyMap['lists']
                            .map((item) {
                                return ConversationData.formMap(item);
                            })
                            .toList();

    } else {
      throw Exception('请求失败:${response.statusCode}');
    }
  }

修改为:

  /// 加载会话列表
  Future<List> _loadConversation() async {
    var response = await http.get(ServiceURL.conversationList);
    if (response.statusCode == 200) {
      final bodyMap = json.decode(response.body);
      return bodyMap['lists'].map((item) {
        return ConversationData.formMap(item);
      }).toList();
    } else {
      throw Exception('请求失败:${response.statusCode}');
    }
  }

4.2 ListView 修改

AsyncSnapshot

包含有绑定的 future 回调的数据,也包含异步任务的状态。我们可以用来判断是否展示加载页面。

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(...),
      body: Container(
        color: Colors.yellow,
        child: FutureBuilder(
          // 传入 Future
          future: _loadConversation(),
          builder: (context, AsyncSnapshot snap){
            // 判断请求状态
            if (snap.connectionState != ConnectionState.done) {
              return const Center(
                child: Text('Loading'),
              );
            }
            // 根据结果处理数据展示
            return ListView(
              children: snap.data.map<Widget>((item){
                return ConversationCell(conversation: item);
              }).toList(),
            );
          },
        ),
      ),
    );
  }

4.3 看看效果

五、 结合上面两种方式

结合上面两种方式,这里使用 Future 调整加载数据的函数。

5.1 请求函数修改

  /// 加载会话列表
  Future<List> _loadConversation() async {
    _isLoading = true;
    var response = await http.get(ServiceURL.conversationList);
    _isLoading = false;
    if (response.statusCode == 200) {
      final bodyMap = json.decode(response.body);
      final result = bodyMap['lists'].map((item) {
        return ConversationData.formMap(item);
      }).toList();

      return result;
    } else {
      throw Exception('请求失败:${response.statusCode}');
    }
  }

5.2 回调处理

  @override
  void initState() {
    super.initState();
    _loadConversation().then((value) {
      // value 就是 Future 里 return 的结果
      setState(() {
        _conversations = value;
      });
    });
  }

5.3 异常处理

通过 Future 我们也可以捕捉异常。

  @override
  void initState() {
    super.initState();
    _loadConversation().then((value) {
      setState(() {
        _conversations = value;
      });
    }).catchError((error) {
      print(error);
    });
  }

5.4 设置超时

  @override
  void initState() {
    super.initState();
    _loadConversation().then((value) {
      setState(() {
        _conversations = value;
      });
    }).catchError((error) {
      print(error);
    }).whenComplete(() {
      print('Done');
    }).timeout(Duration(seconds: 10));
  }

六、 保留数据

总结

  • 第一种方式更加接近我们 iOS 开发中的思路

  • 第二种方式 Future & FutureBuilder 更加充分利用 Future 特性

  • 而结合二者的方式则对我这个iOS刚入门Flutter的新手而言,更加熟悉同时也有一些新的体验

完整代码

GitHub

Last updated