使用http包在Flutter中联网

786 阅读5分钟

大多数应用程序必须在互联网上执行网络请求。因此,优雅地处理网络调用很重要,以避免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序列化,自动生成fromJsontoJson 方法,这有助于防止手动定义时可能出现的任何未被注意的错误。

如果你使用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调用返回的帖子的titlebody ,然后我们将初始化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'),
)

你可以用类似的方法触发其余的网络请求方法。

最终的应用程序用户界面看起来像这样。

Example of the final app's UI

测试网络请求

你可以使用Mockito在Dart中测试API请求,这个包可以帮助模拟网络请求,测试你的应用程序是否有效地处理各种类型的请求,包括null和错误响应。

要用Mockito进行测试,请将其添加到你的pubspec.yaml 文件中,并确保你也设置了build_runnerflutter_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运行测试时,我得到了这个结果。

Results of the tests when run in VS Code

结论

http 包有助于在Flutter中执行所有类型的网络请求。它还提供了对Mockito的支持,简化了对API调用的测试。

如果你想对你的请求有更高级的控制,那么你可以使用Dio包,它有助于避免一些模板代码,并支持全局配置、拦截器、变压器和其他功能。

感谢你阅读这篇文章!如果你对这篇文章或例子有任何建议或问题,欢迎在TwitterLinkedIn上与我联系。你可以在我的GitHub上找到示例应用程序的存储库。

The postNetworking in Flutter using the http packageappeared first onLogRocket Blog.