16.第三方库导入与网络数据异步请求与展示
Last updated
Last updated
在 iOS 开发中 我们经常使用 CocoaPods 等工具进行包管理。那么在 flutter 开发中又该如何进行呢?
这是官方提供来一个网络库:http
点击复制
import 'package:http/http.dart' as http;
简单汇总一下Service,便于后续使用。
import 'package:flutter/material.dart';
class ServiceURL {
/// 会话列表
static Uri conversationList = Uri.parse('xxxxxx');
}
这里简单选择在 initState 中进行网络请求,调用移步方法
@override
void initState() {
super.initState();
_loadConversation();
}
/// 加载会话列表
void _loadConversation() async {
var response = await http.get(ServiceURL.conversationList);
print(response.body);
}
这里可以发现,和我们在 iOS
开发中的网络请求的调用形式有很大差别。iOS
开发中我们都是通过回调的形式接收请求结果的。这里使用 await
。
接收到网络数据后我们自然需要进行数据模型的转换。
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'],
);
}
}
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}');
}
}
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,
),
),
],
),
);
}
}
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,
),
),
);
}
除了按照上面这种我们在 iOS 开发中一样常规的思路进行请求和展示外,再介绍另一种可以处理这种场景。
之前的加载方法:
/// 加载会话列表
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}');
}
}
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(),
);
},
),
),
);
}
结合上面两种方式,这里使用 Future 调整加载数据的函数。
/// 加载会话列表
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}');
}
}
@override
void initState() {
super.initState();
_loadConversation().then((value) {
// value 就是 Future 里 return 的结果
setState(() {
_conversations = value;
});
});
}
通过 Future 我们也可以捕捉异常。
@override
void initState() {
super.initState();
_loadConversation().then((value) {
setState(() {
_conversations = value;
});
}).catchError((error) {
print(error);
});
}
@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的新手而言,更加熟悉同时也有一些新的体验