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

### 前言

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

### 一、 http

这是官方提供来一个网络库：[http](https://pub.dev/packages/http)

点击复制

![1](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MI8JgbGh3U6X_oedqkm%2Fuploads%2Fgit-blob-8e6a3d2fbbff8663587479a16849233b1d3aaef6%2F16-01.png?alt=media)

#### 1.1 修改 pubspec.yaml 配置文件

![2](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MI8JgbGh3U6X_oedqkm%2Fuploads%2Fgit-blob-03434752c7a31397b15a19ab69d8c162c63d4072%2F16-02.png?alt=media)

#### 1.2 更新依赖库

![3](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MI8JgbGh3U6X_oedqkm%2Fuploads%2Fgit-blob-0b68d420ad0e06751787c8587d98137f9526e9bf%2F16-03.png?alt=media)

#### 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 效果

![4](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MI8JgbGh3U6X_oedqkm%2Fuploads%2Fgit-blob-e3848b92d6fcb98994744e62714b908386a15e5d%2F16-04.gif?alt=media)

### 四、 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 看看效果

![5](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MI8JgbGh3U6X_oedqkm%2Fuploads%2Fgit-blob-5335b8d3f41b9ea76f5b1adc474558eac7591aa8%2F16-05.gif?alt=media)

### 五、 结合上面两种方式

结合上面两种方式，这里使用 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](https://github.com/RyukieSama/FlutterStudy/blob/master/fake_wechat/lib/widgets/message/message_page.dart)
