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

### 前言

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

### 一、 http

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

点击复制

![1](/files/OulDBa4raYATHruLFERe)

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

![2](/files/sXBZA1XamwJS3IUqtp4P)

#### 1.2 更新依赖库

![3](/files/BWHAStAT3X5CYpRgerIl)

#### 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](/files/5UQedHXkyZ4LTLLrhHd3)

### 四、 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](/files/xvkEbtJ8bTL9IcNeYhXo)

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

结合上面两种方式，这里使用 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)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ryukiedev.gitbook.io/wiki/flutter/16.-di-san-fang-ku-dao-ru-yu-wang-luo-shu-ju-yi-bu-qing-qiu-yu-zhan-shi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
