上一篇讲完如何处理已经得到的资讯数据,今天来看看我们是如何与Web 服务器进行通信的
首先添加依赖:pub.dev:http
...
dependencies:
http: ^0.12.2
...
import 'package:http/http.dart' as http; //在.dart 引用
在专案上对 Android 的 AndroidManifest.xml
,新增网路权限
<uses-permission android:name="android.permission.INTERNET" />
接下来我们就可以进行网路请求了,我们来使用http.get()
方法从JSONPlaceholder 上获取一个样本Album 数据当作范例
这个http.get()
方法会返回一个包含Response
的Future
,此Response
会包含成功从http 请求接收到的数据,接下来就要处理将http.Response 转换成一个自定义的Dart 对象
先创建一个 Album
Model 类,此范例专案规模小,我们来用手动转换JSON当作范例
class Album {
final int id;
final String title;
Album({this.id, this.title});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
id: json['id'],
title: json['title'],
);
}
}
现在,我们需要定义一个从服务器取得Album 数据的方法,fetchAlbum()
函数并返回Future<Album>
,为了实现这个目标,我们需要做以下几步:
dart:convert
library 将Response 转换成一个 json Map
fromJson
方法将 jsonMap
转换成Album
404 Not Found
错误,也同样要抛出异常,而不是返回一个null
,在检查如下所示的snapshot
值的时候,这一点相当重要import 'dart:convert';
Future<Album> fetchAlbum() async {
final response = await http.get('https://jsonplaceholder.typicode.com/albums/1');
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return Album.fromJson(jsonDecode(response.body));
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
我们新建一个StatefulWidget
MyApp,在此控件上获取Response数据
...
class _MyAppState extends State<MyApp> {
Future<Album> futureAlbum;
//覆写initState() 调用获取数据的方法fetch()
@override
void initState() {
super.initState();
futureAlbum = fetchAlbum();
}
为何要在
initState()
中调用fetchAlbum()
?每当Flutter需要改变视图中的一些内容时(这个发生的频率非常高),就会调用
build()
方法。因此,如果你将数据请求置於build()
内部,就会造成大量的无效调用,同时还会拖慢应用程序的速度
为了能够获取数据并在画面上显示,你可以使用FutureBuilder
widget。这个由Flutter提供的FutureBuilder
组件可以让处理异步数据变的非常简单
此时,必须要有两个参数:
Future
,在这个例子中就是刚刚建的futureAlbum
,为调用fetchAlbum()
返回的futurebuilder
函数,同时这也依赖於Future
的状态:loading、success或者是errorFutureBuilder<Album>(
future: futureAlbum,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data.title);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// By default, show a loading spinner.
return CircularProgressIndicator();
},
);
需要注意的是:当snapshot 值不是null 时,
snapshot.hasData
将只返回true
,这就是为什麽要在後端返回404状态码的时候要让fetchAlbum
方法抛出异常。如果fetchAlbum
返回null
的话,spinner会显示不正常
完整程序码
main.dart
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<Album> fetchAlbum() async {
final response =
await http.get('https://jsonplaceholder.typicode.com/albums/1');
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return Album.fromJson(jsonDecode(response.body));
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
class Album {
final int id;
final String title;
Album({this.id, this.title});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
id: json['id'],
title: json['title'],
);
}
}
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Future<Album> futureAlbum;
@override
void initState() {
super.initState();
futureAlbum = fetchAlbum();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fetch Data Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Fetch Data Example'),
),
body: Center(
child: FutureBuilder<Album>(
future: futureAlbum,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data.title);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// By default, show a loading spinner.
return CircularProgressIndicator();
},
),
),
),
);
}
}
我们来使用http.delete()
方法从JSONPlaceholder 上的Album 中删除指定 id
的数据当作范例
Future<Response> deleteAlbum(String id) async {
final http.Response response = await http.delete(
'https://jsonplaceholder.typicode.com/albums/$id',
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
);
return response;
}
该http.delete()
方法返回一个Future
包含的Response
,该deleteAlbum()
方法采用一个id
参数,该参数用於标识要从服务器删除的数据
我们在上面范例的FutureBuilder
新增一个删除数据功能的按钮,当按下该按钮时,将调用该deleteAlbum()
方法,我们传递的id
是从Internet 所得到的数据的id
,这意味着按下按钮将删除从网路上获取的相同数据
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('${snapshot.data?.title ?? 'Deleted'}'),
RaisedButton(
child: Text('Delete Data'),
onPressed: () {
setState(() {
_futureAlbum = deleteAlbum(snapshot.data.id.toString());
});
},
),
],
);
我们在deleteAlmum()
方法中提出删除请求後,可以从deleteAlbum()
方法中返回一个Response,以通知我们的画面说数据已删除
Future<Album> deleteAlbum(String id) async {
final http.Response response = await http.delete(
'https://jsonplaceholder.typicode.com/albums/$id',
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
);
if (response.statusCode == 200) {
// If the server returned a 200 OK response,
// then parse the JSON. After deleting,
// you'll get an empty JSON `{}` response.
// Don't return `null`, otherwise
// `snapshot.hasData` will always return false
// on `FutureBuilder`.
return Album.fromJson(jsonDecode(response.body));
} else {
throw Exception('Failed to delete album.');
}
}
FutureBuilder()
现在在收到Response 时进行重建。由於如果删除的请求成功,Response 的主体内容中将没有任何数据,因此该Album.fromJson()
方法将创建具有Album
默认值的对象实例
更新後的完整程序码:
main.dart
:
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<Album> fetchAlbum() async {
final response =
await http.get('https://jsonplaceholder.typicode.com/albums/1');
if (response.statusCode == 200) {
// If the server did return a 200 OK response, then parse the JSON.
return Album.fromJson(jsonDecode(response.body));
} else {
// If the server did not return a 200 OK response, then throw an exception.
throw Exception('Failed to load album');
}
}
Future<Album> deleteAlbum(String id) async {
final http.Response response = await http.delete(
'https://jsonplaceholder.typicode.com/albums/$id',
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
);
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON. After deleting,
// you'll get an empty JSON `{}` response.
// Don't return `null`, otherwise `snapshot.hasData`
// will always return false on `FutureBuilder`.
return Album.fromJson(jsonDecode(response.body));
} else {
// If the server did not return a "200 OK response",
// then throw an exception.
throw Exception('Failed to delete album.');
}
}
class Album {
final int id;
final String title;
Album({this.id, this.title});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
id: json['id'],
title: json['title'],
);
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
@override
_MyAppState createState() {
return _MyAppState();
}
}
class _MyAppState extends State<MyApp> {
Future<Album> _futureAlbum;
@override
void initState() {
super.initState();
_futureAlbum = fetchAlbum();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Delete Data Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Delete Data Example'),
),
body: Center(
child: FutureBuilder<Album>(
future: _futureAlbum,
builder: (context, snapshot) {
// If the connection is done,
// check for response data or an error.
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasData) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('${snapshot.data?.title ?? 'Deleted'}'),
RaisedButton(
child: Text('Delete Data'),
onPressed: () {
setState(() {
_futureAlbum =
deleteAlbum(snapshot.data.id.toString());
});
},
),
],
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
}
// By default, show a loading spinner.
return CircularProgressIndicator();
},
),
),
),
);
}
}
我们来使用http.put()
方法从JSONPlaceholder 上的Album 中更新指定栏位的数据当作范例
Future<http.Response> updateAlbum(String title) {
return http.put(
'https://jsonplaceholder.typicode.com/albums/1',
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{
'title': title,
}),
);
}
该http.put()
方法返回一个Future
包含的Response
,该updateAlbum
方法接受一个参数,该参数title
被发送到服务器以更新Album
updateAlbum()
函数返回 Future<Album>
,概念与上述例子相同:
Map
使用 dart:convert
将Response 主体转换为JSONUPDATED
状态码为200的Response,则使用工厂方法将JSONMap
转换为AlbumfromJson()
UPDATED
状态码为200的Response,则引发异常。(即使在404未找到服务器Response的情况下,也将引发异常,请勿返回null
,这在检查取得的结果数据(snapshot
)时很重要Future<Album> updateAlbum(String title) async {
final http.Response response = await http.put(
'https://jsonplaceholder.typicode.com/albums',
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{
'title': title,
}),
);
if (response.statusCode == 200) {
// If the server did return a 200 UPDATED response,
// then parse the JSON.
return Album.fromJson(jsonDecode(response.body));
} else {
// If the server did not return a 200 UPDATED response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
给使用者自己更新标题栏位
创建一个TextField
以输入标题,并创建一个RaisedButton
用来更新服务器上的数据。还定义一个TextEditingController
以从读取用户输入TextField
的值,以调用updateAlbum()
方法
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
...
RaisedButton(
child: Text('Update Data'),
onPressed: () {
setState(() {
_futureAlbum = updateAlbum(_controller.text);
});
},
),
...
],
)
完整的程序码:
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<Album> fetchAlbum() async {
final response =
await http.get('https://jsonplaceholder.typicode.com/albums/1');
if (response.statusCode == 200) {
// If the server did return a 200 OK response, then parse the JSON.
return Album.fromJson(jsonDecode(response.body));
} else {
// If the server did not return a 200 OK response, then throw an exception.
throw Exception('Failed to load album');
}
}
Future<Album> deleteAlbum(String id) async {
final http.Response response = await http.delete(
'https://jsonplaceholder.typicode.com/albums/$id',
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
);
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON. After deleting,
// you'll get an empty JSON `{}` response.
// Don't return `null`, otherwise `snapshot.hasData`
// will always return false on `FutureBuilder`.
return Album.fromJson(jsonDecode(response.body));
} else {
// If the server did not return a "200 OK response",
// then throw an exception.
throw Exception('Failed to delete album.');
}
}
Future<Album> updateAlbum(String title) async {
final http.Response response = await http.put(
'https://jsonplaceholder.typicode.com/albums/1',
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{
'title': title,
}),
);
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return Album.fromJson(jsonDecode(response.body));
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to update album.');
}
}
class Album {
final int id;
final String title;
Album({this.id, this.title});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
id: json['id'],
title: json['title'],
);
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
@override
_MyAppState createState() {
return _MyAppState();
}
}
class _MyAppState extends State<MyApp> {
final TextEditingController _controller = TextEditingController();
Future<Album> _futureAlbum;
@override
void initState() {
super.initState();
_futureAlbum = fetchAlbum();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Update Data Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Update Data Example'),
),
body: Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(8.0),
child: FutureBuilder<Album>(
future: _futureAlbum,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasData) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('${snapshot.data?.title ?? 'Deleted'}'),
TextField(
controller: _controller,
decoration: InputDecoration(hintText: 'Enter Title'),
),
RaisedButton(
child: Text('Update Data'),
onPressed: () {
setState(() {
_futureAlbum = updateAlbum(_controller.text);
});
},
),
RaisedButton(
child: Text('Delete Data'),
onPressed: () {
setState(() {
_futureAlbum =
deleteAlbum(snapshot.data.id.toString());
});
},
),
],
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
}
return CircularProgressIndicator();
},
),
),
),
);
}
}
我们来使用http.post()
方法将Album 标题发送给JSONPlaceholder
Future<http.Response> createAlbum(String title) {
return http.post(
'https://jsonplaceholder.typicode.com/albums',
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{
'title': title,
}),
);
}
该createAlbum()
方法采用一个参数title
,该参数发送到服务器以创建一个Album
做法都跟前面一样,差别在於CREATED
成功的话,预期的状态码为201
完整的程序码:
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<Album> createAlbum(String title) async {
final http.Response response = await http.post(
'https://jsonplaceholder.typicode.com/albums',
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{
'title': title,
}),
);
if (response.statusCode == 201) {
return Album.fromJson(jsonDecode(response.body));
} else {
throw Exception('Failed to create album.');
}
}
class Album {
final int id;
final String title;
Album({this.id, this.title});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
id: json['id'],
title: json['title'],
);
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
@override
_MyAppState createState() {
return _MyAppState();
}
}
class _MyAppState extends State<MyApp> {
final TextEditingController _controller = TextEditingController();
Future<Album> _futureAlbum;
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Create Data Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Create Data Example'),
),
body: Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(8.0),
child: (_futureAlbum == null)
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
controller: _controller,
decoration: InputDecoration(hintText: 'Enter Title'),
),
RaisedButton(
child: Text('Create Data'),
onPressed: () {
setState(() {
_futureAlbum = createAlbum(_controller.text);
});
},
),
],
)
: FutureBuilder<Album>(
future: _futureAlbum,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data.title);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return CircularProgressIndicator();
},
),
),
),
);
}
}
为了从众多的网络服务中获取数据,你需要提供相应的授权认证信息。当然了,解决这一问题的方法有很多,而最常见的方法或许就是使用Authorization
HTTP header了
添加Authorization Headers:
http
这个package提供了相当实用的方法来向请求中添加headers,你也可以使用dart:io
来使用一些常见的HttpHeaders
Future<http.Response> fetchAlbum() {
return http.get(
'https://jsonplaceholder.typicode.com/albums/1',
// Send authorization headers to the backend.
headers: {HttpHeaders.authorizationHeader: "Basic your_api_token_here"},
);
}
完整程序码:
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
Future<Album> fetchAlbum() async {
final response = await http.get(
'https://jsonplaceholder.typicode.com/albums/1',
headers: {HttpHeaders.authorizationHeader: "Basic your_api_token_here"},
);
final responseJson = jsonDecode(response.body);
return Album.fromJson(responseJson);
}
class Album {
final int userId;
final int id;
final String title;
Album({this.userId, this.id, this.title});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
userId: json['userId'],
id: json['id'],
title: json['title'],
);
}
}
除了普通的HTTP请求,你还可以通过WebSockets
来连接服务器, WebSockets
可以以非轮询的方式与服务器进行双向通信
在这里,你可以连接一个 由websocket.org提供的测试服务器。该服务器只会返回你发送的信息
连接WebSocket 服务器
web_socket_channel
这个package 提供了连接WebSocket 服务器所需的一些工具
该包提供的WebSocketChannel
不仅可以让你监听到来自服务器的消息还可以让你向服务器推送消息
首先添加依赖:pub.dev:http
...
dependencies:
web_socket_channel: ^1.1.0
...
在Flutter中,只用一行代码就可以创建一个连接到服务器的WebSocketChannel
:
final channel = IOWebSocketChannel.connect('ws://echo.websocket.org');
监听来自服务器的资讯
建立了连接之後,你就可以监听来自服务器的消息了
当你向测试服务器发送一条消息之後,它会将同样的消息发送回来
此范例,我们用StreamBuilder
组件来监听新消息,并使用Text
组件来展示它们
StreamBuilder(
stream: widget.channel.stream,
builder: (context, snapshot) {
return Text(snapshot.hasData ? '${snapshot.data}' : '');
},
);
运作方式:
WebSocketChannel
提供了一个来自服务器的Stream
类消息这个
Stream
类是dart:async
包的基本组成部分,它提供了一个从数据源监听异步事件的方法。和Future
不一样的是,Future
只能返回一个单独的异步响应,而Stream
类可以随着时间的推移传递很多事
StreamBuilder
widget会和Stream
建立起连接,并且每当它接收到一个使用给定builder()
函数的事件时,就会通知Flutter去rebuild
向服务器发送数据
要向服务器发送数据,可以使用WebSocketChannel
提供的sink
下的add()
方法来发送信息
WebSocketChannel
提供了一个StreamSink
来向服务器推送消息。这个
StreamSink
类提供了一个可以向数据源添加同步或者异步事件的通用方法
关闭WebSocket 连接
当你使用完WebSocket之後,记得关闭这个连接。要关闭这个WebSocket连接,只需要关闭sink
channel.sink.close();
完整的范例程序码:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:web_socket_channel/io.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final title = 'WebSocket Demo';
return MaterialApp(
title: title,
home: MyHomePage(
title: title,
channel: IOWebSocketChannel.connect('ws://echo.websocket.org'),
),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
final WebSocketChannel channel;
MyHomePage({Key key, @required this.title, @required this.channel})
: super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController _controller = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Form(
child: TextFormField(
controller: _controller,
decoration: InputDecoration(labelText: 'Send a message'),
),
),
StreamBuilder(
stream: widget.channel.stream,
builder: (context, snapshot) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 24.0),
child: Text(snapshot.hasData ? '${snapshot.data}' : ''),
);
},
)
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _sendMessage,
tooltip: 'Send message',
child: Icon(Icons.send),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
void _sendMessage() {
if (_controller.text.isNotEmpty) {
widget.channel.sink.add(_controller.text);
}
}
@override
void dispose() {
widget.channel.sink.close();
super.dispose();
}
}
Dart 通常只会在单线程中处理它们的工作,并且在大多数情况中,基本不会出现像动画卡顿以及性能不足这种问题,但是,当你需要进行一个非常复杂的计算时,例如解析一个巨大的JSON 文档。如果这项工作耗时超过了16 毫秒,那麽你的用户就会感受到掉帧。
为了避免掉帧,像上面那样消耗性能的计算就应该放在後台处理。在Android平台上,这意味着你需要在不同的线程中进行调度工作。而在Flutter中,你可以使用一个单独的Isolate
在这个例子中,你将会使用http.get()
方法通过 JSONPlaceholder REST API获取到一个包含5000张图片对象的超大JSON文档
Future<http.Response> fetchPhotos(http.Client client) async {
return client.get('https://jsonplaceholder.typicode.com/photos');
}
在这个例子中你需要给方法添加了一个
http.Client
参数。这将使得该方法测试起来更容易同时也可以在不同环境中使用
接下来需要解析并将json 转换成一列图片
首先创建一个Photo
Model 类:
class Photo {
final int id;
final String title;
final String thumbnailUrl;
Photo({this.id, this.title, this.thumbnailUrl});
factory Photo.fromJson(Map<String, dynamic> json) {
return Photo(
id: json['id'] as int,
title: json['title'] as String,
thumbnailUrl: json['thumbnailUrl'] as String,
);
}
}
现在,为了让fetchPhotos()
方法可以返回一个 Future<List<Photo>>
,来将Response 转换成一列图片,我们需要以下两点更新:
List<Photo>
的方法:parsePhotos()
fetchPhotos()
方法中使用parsePhotos()
方法// A function that converts a response body into a List<Photo>.
List<Photo> parsePhotos(String responseBody) {
final parsed = jsonDecode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<Photo>((json) => Photo.fromJson(json)).toList();
}
Future<List<Photo>> fetchPhotos(http.Client client) async {
final response =
await client.get('https://jsonplaceholder.typicode.com/photos');
return parsePhotos(response.body);
}
将部分工作移交到单独的isolate
中:
如果你在一台很慢的手机上运行fetchPhotos()
函数,你或许会注意到应用会有点卡顿,因为它需要解析并转换json。显然这并不好,所以你要避免它
通过Flutter提供的compute()
方法将解析和转换的工作移交到一个後台isolate中。这个compute()
函数可以在後台isolate中运行复杂的函数并返回结果。在这里,我们就需要将parsePhotos()
方法放入後台
Future<List<Photo>> fetchPhotos(http.Client client) async {
final response =
await client.get('https://jsonplaceholder.typicode.com/photos');
// Use the compute function to run parsePhotos in a separate isolate.
return compute(parsePhotos, response.body);
}
使用Isolates 需要注意的地方:
Isolates通过来回传递消息来交流。这些消息可以是任何值,它们可以是null
、num
、bool
、double
或者String
,哪怕是像这个例子中的List<Photo>
这样简单对像都没问题。
当你试图传递更复杂的对象时,你可能会遇到错误,例如在isolates之间的Future
或者http.Response
完整范例程序码:
import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<List<Photo>> fetchPhotos(http.Client client) async {
final response =
await client.get('https://jsonplaceholder.typicode.com/photos');
// Use the compute function to run parsePhotos in a separate isolate.
return compute(parsePhotos, response.body);
}
// A function that converts a response body into a List<Photo>.
List<Photo> parsePhotos(String responseBody) {
final parsed = jsonDecode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<Photo>((json) => Photo.fromJson(json)).toList();
}
class Photo {
final int albumId;
final int id;
final String title;
final String url;
final String thumbnailUrl;
Photo({this.albumId, this.id, this.title, this.url, this.thumbnailUrl});
factory Photo.fromJson(Map<String, dynamic> json) {
return Photo(
albumId: json['albumId'] as int,
id: json['id'] as int,
title: json['title'] as String,
url: json['url'] as String,
thumbnailUrl: json['thumbnailUrl'] as String,
);
}
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final appTitle = 'Isolate Demo';
return MaterialApp(
title: appTitle,
home: MyHomePage(title: appTitle),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
MyHomePage({Key key, this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: FutureBuilder<List<Photo>>(
future: fetchPhotos(http.Client()),
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData
? PhotosList(photos: snapshot.data)
: Center(child: CircularProgressIndicator());
},
),
);
}
}
class PhotosList extends StatelessWidget {
final List<Photo> photos;
PhotosList({Key key, this.photos}) : super(key: key);
@override
Widget build(BuildContext context) {
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
itemCount: photos.length,
itemBuilder: (context, index) {
return Image.network(photos[index].thumbnailUrl);
},
);
}
}
今天的内容为该如何简单制作出一个自动攻击的敌人 ...
图片来源 接下来继续谈到Q2的目标是Azure Certificate, 云端相关证照, 因为我非...
仙女棒 ( 光迹效果 ) 教学原文参考:仙女棒 ( 光迹效果 ) 这篇文章会介绍,如何在 Scrat...
本篇文章同步发表在 HKT 线上教室 部落格,线上影音教学课程已上架至 Udemy 和 Youtu...
笔者前阵子蛮喜欢路跑的,但跑了很久,成绩却一直没有明显进步,为此感到因扰。後来有一天,一位朋友跟我说...