大多数应用程序必须在互联网上执行网络请求。因此,优雅地处理网络调用很重要,以避免API调用中出现不必要的错误。
在这篇文章中,我们将看看我们如何使用http 包在Flutter中处理REST API请求。
开始吧
使用以下命令创建一个新的Flutter项目。
flutter create flutter_http_networking
你可以用你喜欢的IDE来打开这个项目,但在这个例子中,我将使用VS Code。
code flutter_http_networking
将该 [http](https://pub.dev/packages/http)包到你的pubspec.yaml 文件。
dependencies:
http: ^0.13.3
将你的main.dart 文件的内容替换为以下基本结构。
import 'package:flutter/material.dart';
import 'screens/home_page.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Networking',
theme: ThemeData(
primarySwatch: Colors.teal,
),
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}
我们将在看了执行网络操作的API后,创建HomePage 。
请求API数据
对于这个API请求的演示,我们将使用来自JSONPlaceholder的样本/posts 数据。我们将首先使用GET请求来获取一个单一的帖子数据。你必须使用的端点是。
GET https://jsonplaceholder.typicode.com/posts/<id>
在这里,你必须用一个代表你想检索的帖子ID的整数值替换<id> 。
如果你的请求成功,你将收到的JSON响应样本将是这样的。
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
指定模型类
模型类可以帮助你打包由API调用返回的数据,或使用网络请求整齐地发送数据。
我们将定义一个模型类来处理一个单一的帖子数据。你可以使用像quicktype这样的JSON-Dart类转换工具来轻松生成一个模型类。将其复制并粘贴到一个名为post.dart 的文件中。
class Post {
Post({
this.id,
this.userId,
this.title,
this.body,
});
int? id;
int? userId;
String? title;
String? body;
factory Post.fromJson(Map<String, dynamic> json) => Post(
userId: json["userId"],
id: json["id"],
title: json["title"],
body: json["body"],
);
Map<String, dynamic> toJson() => {
"userId": userId,
"id": id,
"title": title,
"body": body,
};
}
另外,你也可以使用JSON序列化,自动生成fromJson 和toJson 方法,这有助于防止手动定义时可能出现的任何未被注意的错误。
如果你使用JSON序列化,你将需要以下软件包。
-
[json_serializable](https://pub.dev/packages/json_serializable)[json_annotation](https://pub.dev/packages/json_annotation)[build_runner](https://pub.dev/packages/build_runner)
将它们添加到你的pubspec.yaml 文件中。
dependencies:
json_annotation: ^4.0.1
dev_dependencies:
json_serializable: ^4.1.3
build_runner: ^2.0.4
为了使用JSON序列化,你必须修改Post 类,如下所示。
import 'package:json_annotation/json_annotation.dart';
part 'post.g.dart';
@JsonSerializable()
class Post {
Post({
this.id,
this.userId,
this.title,
this.body,
});
int? id;
int? userId;
String? title;
String? body;
factory Post.fromJson(Map<String, dynamic> json) => _$PostFromJson(json);
Map<String, dynamic> toJson() => _$PostToJson(this);
}
你可以使用以下命令来触发代码生成工具。
flutter pub run build_runner build
如果你想保持代码生成工具在后台运行--它将自动应用你对模型类的任何进一步修改--使用命令。
flutter pub run build_runner serve --delete-conflicting-outputs
--delete-conflicting-outputs 标志有助于在发现任何冲突时重新生成生成类的一部分。
执行API请求
现在你可以开始在REST API上执行各种网络请求。为了保持你的代码简洁,你可以在一个单独的类中定义与网络请求有关的方法。
创建一个名为post_client.dart 的新文件,并在其中定义PostClient 类。
class PostClient {
// TODO: Define the methods for network requests
}
在变量中定义服务器的基本URL和所需的端点。
class PostClient {
static final baseURL = "https://jsonplaceholder.typicode.com";
static final postsEndpoint = baseURL + "/posts";
}
我们将在执行请求时使用这些变量。
获取数据
你可以使用GET请求来从API中获取信息。要获取一个单一的帖子数据,你可以定义一个类似这样的方法。
Future<Post> fetchPost(int postId) async {
final url = Uri.parse(postsEndpoint + "/$postId");
final response = await http.get(url);
}
这个方法试图根据传递给它的ID来检索一个帖子数据。http.get() 方法使用URL从服务器上获取数据,这些数据被存储在response 变量中。
通过检查HTTP状态代码来验证请求是否成功,如果成功的话应该是200 。现在你可以对原始的JSON数据进行解码,并使用Post.fromJson() ,用模型类以一种漂亮的、结构化的方式来存储它。
Future<Post> fetchPost(int postId) async {
final url = Uri.parse(postsEndpoint + "/$postId");
final response = await http.get(url);
if (response.statusCode == 200) {
return Post.fromJson(jsonDecode(response.body));
} else {
throw Exception('Failed to load post: $postId');
}
}
发送数据
你可以使用POST请求来发送数据到API。我们将通过使用以下方法发送数据来创建一个新的帖子。
Future<Post> createPost(String title, String body) async {
final url = Uri.parse(postsEndpoint);
final response = await http.post(
url,
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode({
'title': title,
'body': body,
}),
);
}
在发送数据时,你必须在headers 中指定头的类型,以及你想发送到指定端点的body 。另外,JSON数据应使用jsonEncode 方法以编码的格式发送。
你可以使用HTTP状态代码检查你的POST请求是否成功。如果它返回的状态码是201 ,那么请求是成功的,我们可以返回帖子数据。
Future<Post> createPost(String title, String body) async {
// ...
if (response.statusCode == 201) {
return Post.fromJson(jsonDecode(response.body));
} else {
throw Exception('Failed to create post');
}
}
更新数据
你可以通过使用PUT请求来更新API服务器上的任何帖子信息。像这样定义该方法。
Future<Post> updatePost(int postId, String title, String body) async {
final url = Uri.parse(postsEndpoint + "/$postId");
final response = await http.put(
url,
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode({
'title': title,
'body': body,
}),
);
}
在这里,我们使用了帖子的ID来指定要更新的帖子,并将请求发送给它。如果响应状态代码是200 ,那么请求是成功的,你可以返回服务器发送的更新帖子。
Future<Post> updatePost(int postId, String title, String body) async {
// ...
if (response.statusCode == 200) {
return Post.fromJson(jsonDecode(response.body));
} else {
throw Exception('Failed to update post');
}
}
删除数据
你可以通过使用DELETE请求从API服务器上删除一个帖子。该方法可以定义如下。
Future<Post> deletePost(int postId) async {
final url = Uri.parse(postsEndpoint + "/$postId");
final response = await http.delete(
url,
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
);
if (response.statusCode == 200) {
return Post.fromJson(jsonDecode(response.body));
} else {
throw Exception('Failed to delete post: $postId');
}
}
我们用帖子的ID来指定要删除的帖子,并将请求发送到相应的端点。你可以通过检查HTTP状态代码是否为200 来验证请求是否成功。
你应该注意,在删除的情况下,响应会返回空数据。
Future<Post> deletePost(int postId) async {
//...
if (response.statusCode == 200) {
return Post.fromJson(jsonDecode(response.body));
} else {
throw Exception('Failed to delete post: $postId');
}
}
构建用户界面
用户界面将被定义在HomePage widget内。它将是一个StatefulWidget ,因为我们需要在每个网络请求后更新其状态。
import 'package:flutter/material.dart';
import 'package:flutter_http_networking/utils/post_client.dart';
import 'package:http/http.dart' as http;
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold();
}
}
首先,我们将定义两个变量,它们将存储由API调用返回的帖子的title 和body ,然后我们将初始化PostClient 类。
import 'package:flutter/material.dart';
import 'package:flutter_http_networking/utils/post_client.dart';
import 'package:http/http.dart' as http;
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final PostClient _postClient = PostClient();
String? _postTitle;
String? _postBody;
@override
Widget build(BuildContext context) {
return Scaffold();
}
}
现在我们将创建按钮,触发网络请求方法,并用服务器返回的信息更新变量。下面是触发fetchPost() 方法的代码片断。
ElevatedButton(
onPressed: () async {
final post = await _postClient.fetchPost(1);
setState(() {
_postTitle = post.title;
_postBody = post.body;
});
},
child: Text('GET'),
)
你可以用类似的方法触发其余的网络请求方法。
最终的应用程序用户界面看起来像这样。
测试网络请求
你可以使用Mockito在Dart中测试API请求,这个包可以帮助模拟网络请求,测试你的应用程序是否有效地处理各种类型的请求,包括null和错误响应。
要用Mockito进行测试,请将其添加到你的pubspec.yaml 文件中,并确保你也设置了build_runner 和flutter_test 的依赖关系。
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.0.4
mockito: ^5.0.10
现在,你必须对你要测试的网络请求方法做一个小小的修改。我们将对fetchPost() 方法进行修改。
为该方法提供一个http.Client ,并使用客户端来执行get() 的请求。修改后的方法将看起来像这样。
Future<Post> fetchPost(http.Client client, int postId) async {
final url = Uri.parse(postsEndpoint + "/$postId");
final response = await client.get(url);
if (response.statusCode == 200) {
return Post.fromJson(jsonDecode(response.body));
} else {
throw Exception('Failed to load post: $postId');
}
}
在test 文件夹中创建一个名为fetch_post_test.dart 的测试文件,并在主函数中注上@GenerateMocks([http.Client]) 。
import 'package:flutter_http_networking/models/post.dart';
import 'package:flutter_http_networking/utils/post_client.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'fetch_post_test.mocks.dart';
@GenerateMocks([http.Client])
void main() {}
这将有助于使用build_runner 工具生成MockClient 类。通过使用以下命令触发代码生成。
flutter pub run build_runner build
我们将只定义两个测试:一个是成功的API请求,一个是带有错误的不成功的请求。你可以使用Mockito提供的when() 函数来测试这些条件。
@GenerateMocks([http.Client])
void main() {
PostClient _postClient = PostClient();
final postEndpoint =
Uri.parse('https://jsonplaceholder.typicode.com/posts/1');
group('fetchPost', () {
test('successful request', () async {
final client = MockClient();
when(
client.get(postEndpoint),
).thenAnswer((_) async => http.Response(
'{"userId": 1, "id": 2, "title": "mock post", "body": "post body"}',
200));
expect(await _postClient.fetchPost(client, 1), isA<Post>());
});
test('unsuccessful request', () {
final client = MockClient();
when(
client.get(postEndpoint),
).thenAnswer((_) async => http.Response('Not Found', 404));
expect(_postClient.fetchPost(client, 1), throwsException);
});
});
}
你可以使用命令来运行这些测试。
flutter test test/fetch_post_test.dart
或者,你也可以直接在你的IDE中运行测试。当我使用VS Code运行测试时,我得到了这个结果。
结论
http 包有助于在Flutter中执行所有类型的网络请求。它还提供了对Mockito的支持,简化了对API调用的测试。
如果你想对你的请求有更高级的控制,那么你可以使用Dio包,它有助于避免一些模板代码,并支持全局配置、拦截器、变压器和其他功能。
感谢你阅读这篇文章!如果你对这篇文章或例子有任何建议或问题,欢迎在Twitter或LinkedIn上与我联系。你可以在我的GitHub上找到示例应用程序的存储库。
The postNetworking in Flutter using the http packageappeared first onLogRocket Blog.