在 Flutter 中使用 http
相信大家在使用应用的时候都发现了,
大多数应用程序必须通过互联网执行网络请求。因此,优雅地处理网络调用以避免 API 调用中出现不必要的错误非常重要。
在本文中,我们将看看如何在 Flutter 中使用http
包处理 REST API 请求。
这也是非常重要的一个环节,如果你想与后端交互,就不得不用到它,当然,还有另外一个 dio 包,我也会在后面给大家做介绍。
入门
使用以下命令创建一个新的 Flutter 项目:
flutter create flutter_http_networking
复制代码
您可以使用自己喜欢的 IDE 打开项目,但在本示例中,我将使用 VS Code:
code flutter_http_networking
复制代码
将http
包添加到您的pubspec.yaml
文件中:
在这里,我将 http 包的文档放到这儿,以供大家查阅https://pub.flutter-io.cn/packages/http 在 pub.dev 有 3600+喜欢,所以大家可以看到他的实力。
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的示例数据。这是一个用于测试和原型制作的免费假 API。
我们将首先使用 GET 请求获取单个帖子数据。您必须使用的端口是:/posts
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 moestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto" }
复制代码
指定模型类
模型类可帮助您打包 API 调用返回的数据或使用网络请求整齐地发送数据。
我们将定义一个模型类来处理单个帖子数据。您可以使用JSON-to-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
json_annotation
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 检索帖子数据。该方法使用 URL 从服务器获取存储在变量中的数据。http.get()``response
通过检查 HTTP 状态代码来验证请求是否成功,如果成功应该是200
。您现在可以使用 Post.fromJson()解码原始 JSON 数据,并使用模型类以良好的结构化方式存储它。``
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 发送数据。我们将通过使用以下方法发送数据来创建一个新帖子:
uture<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
,则请求成功,我们可以返回 post 数据。
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
小部件中定义。这将是一个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(); } }
首先,我们将定义将存储的两个变量title
,和body
并通过 API 调用返回的帖子,然后我们会初始化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'), )
您可以类似地触发其余的网络请求方法。
最终的应用程序 UI 如下所示:
测试网络请求
您可以使用Mockito测试 Dart 中的 API 请求,该包有助于模拟网络请求并测试您的应用程序是否有效处理各种类型的请求,包括空响应和错误响应。
要使用 Mockito 进行测试,请将其添加到您的文件中,并确保您有如下依赖
dev_dependencies: flutter_test: sdk: flutter build_runner: ^2.0.4 mockito: ^5.0.10
复制代码
现在,您必须对要测试的网络请求方法进行一些小的修改。我们将对方法进行修改。为fetchPost()
方法提供一个get()
,并使用http.Client
客户端来执行请求。修改后的方法将如下所示:
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'); } }
创建一个fetch_post_test.dart
文件在test
夹内调用的测试文件,并添加方法.@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() {}
这将有助于MockClient
使用该build_runner
工具生成类。使用以下命令触发代码生成:
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 包,它有助于避免一些样板代码,并支持全局配置、拦截器、转换器和其他功能。