在鸿蒙上使用Flutter开发应用时,网络请求是绕不开的基础能力。相比ArkTS的@ohos.net.http模块(回调风格),Flutter的http包凭借简洁的async/await写法更受开发者青睐。本文以一个完整的Demo为例,记录在鸿蒙Flutter项目中用http包调用REST API的实践过程,重点梳理参数配置、状态管理以及常见踩坑点。
- dependencies:
- http: ^1.2.2
复制代码 添加依赖后,无需额外配置即可在鸿蒙设备上运行。如需拦截器、请求取消等高级功能,可后续迁移至dio。
GET请求:拉取列表
最基本的操作是发送GET请求并解析JSON。代码如下:- import 'dart:convert';
- import 'package:http/http.dart' as http;
- Future<void> _fetchPosts() async {
- final resp = await http.get(
- Uri.parse('https://jsonplaceholder.typicode.com/posts?_limit=20'),
- );
- if (resp.statusCode == 200) {
- final list = jsonDecode(resp.body) as List;
- final posts = list.map((e) => _Post.fromJson(e)).toList();
- }
- }
复制代码 http.get返回Response对象,statusCode为状态码,body为响应体字符串。jsonDecode将JSON字符串转换为Dart的Map或List,再通过fromJson工厂构造函数转为强类型Model。
Model类封装
建议将JSON解析封装为Model类,避免业务代码中散落json['xxx']:- class Post {
- final int id;
- final String title;
- final String body;
- Post({required this.id, required this.title, required this.body});
- factory Post.fromJson(Map<String, dynamic> json) {
- return Post(
- id: json['id'] as int,
- title: json['title'] as String,
- body: json['body'] as String,
- );
- }
- }
复制代码 字段较多时可使用json_serializable自动生成,但小规模手写更直观。
状态管理:loading / success / error
网络请求至少需要三种状态:加载中、成功、失败。使用枚举管理比布尔值更清晰:- enum _LoadState { idle, loading, success, error }
- _LoadState _state = _LoadState.idle;
- List<_Post> _posts = [];
- String _error = '';
复制代码 UI根据状态渲染不同组件:- Widget _buildBody() {
- switch (_state) {
- case _LoadState.loading:
- return const Center(child: CircularProgressIndicator());
- case _LoadState.error:
- return _buildError();
- case _LoadState.success:
- case _LoadState.idle:
- return _posts.isEmpty ? _buildEmpty() : _buildList();
- }
- }
复制代码
GET详情与POST请求
点选列表项获取详情:- Future<void> _fetchDetail(int id) async {
- final resp = await http.get(
- Uri.parse('https://jsonplaceholder.typicode.com/posts/$id'),
- );
- if (resp.statusCode == 200) {
- final data = jsonDecode(resp.body);
- // 使用 data['title'], data['body']
- }
- }
复制代码 POST创建资源需设置Content-Type头部:- Future<void> _createPost() async {
- final resp = await http.post(
- Uri.parse('https://jsonplaceholder.typicode.com/posts'),
- headers: {'Content-Type': 'application/json'},
- body: jsonEncode({
- 'title': '鸿蒙Flutter测试',
- 'body': '通过http.post创建',
- 'userId': 1,
- }),
- );
- if (resp.statusCode == 201) {
- final data = jsonDecode(resp.body);
- // data['id'] 为新ID
- }
- }
复制代码 注意:POST成功通常返回201 Created而非200 OK。
DELETE与超时控制
DELETE请求最简:- Future<void> _deletePost(Post post) async {
- final resp = await http.delete(
- Uri.parse('https://jsonplaceholder.typicode.com/posts/${post.id}'),
- );
- if (resp.statusCode == 200) {
- // 从列表移除
- }
- }
复制代码 超时控制使用Dart的timeout扩展方法:- final resp = await http
- .get(Uri.parse('https://jsonplaceholder.typicode.com/posts'))
- .timeout(const Duration(seconds: 10));
复制代码 超时会抛出TimeoutException,建议在catch中统一处理。
错误友好化
直接显示系统错误信息体验差,可根据异常字符串匹配友好提示:- String _friendlyError(Object e) {
- final msg = e.toString();
- if (msg.contains('SocketException') || msg.contains('Failed host')) {
- return '网络连接失败,请检查网络';
- }
- if (msg.contains('TimeoutException')) {
- return '请求超时,请稍后重试';
- }
- if (msg.contains('HandshakeException')) {
- return 'SSL握手失败';
- }
- return msg;
- }
复制代码
下拉刷新与ApiClient封装
使用RefreshIndicator包裹可滚动组件:- RefreshIndicator(
- onRefresh: _fetchPosts,
- child: _buildList(),
- )
复制代码 注意child必须为ListView等可滚动组件,Column无法触发下拉手势。
项目规模扩大后建议封装ApiClient,统一管理baseUrl、headers和异常:- class ApiClient {
- static const _baseUrl = 'https://jsonplaceholder.typicode.com';
- static Future<List<Post>> getPosts() async {
- final resp = await http.get(Uri.parse('$_baseUrl/posts?_limit=20'));
- if (resp.statusCode == 200) {
- final list = jsonDecode(resp.body) as List;
- return list.map((e) => Post.fromJson(e)).toList();
- }
- throw ApiException(resp.statusCode, resp.body);
- }
- }
- class ApiException implements Exception {
- final int statusCode;
- final String body;
- ApiException(this.statusCode, this.body);
- }
复制代码
与ArkTS网络请求对比
ArkTS的@ohos.net.http使用Promise链式调用,对比Flutter的async/await风格,后者代码更接近同步写法。但ArkTS原生支持自定义CA证书和配置校验,企业内网场景更方便;Flutter需通过HttpClient手动设置SecurityContext,稍复杂。
鸿蒙专属踩坑集锦
- 忘记设置Content-Type:POST请求若不设置`'Content-Type': 'application/json'`,服务端返回400 Bad Request。
- 状态码判断错误:创建资源成功应判断201 Created而非200 OK。
- jsonDecode返回类型:返回dynamic,若直接强转Map可能失败(当JSON顶层为数组时)。应先检查类型:
- final data = jsonDecode(resp.body);
- if (data is List) {
- // 处理数组
- } else if (data is Map) {
- // 处理对象
- }
复制代码 - mounted检查遗漏:异步回调中调用setState前需判断 mounted:
- if (!mounted) return;
- setState(() { ... });
复制代码 - 网络权限缺失:鸿蒙应用必须在module.json5中声明ohos.permission.INTERNET权限,否则请求被拦截,报SocketException。配置如下:
- {
- "name": "ohos.permission.INTERNET",
- "reason": "$string:reason_internet",
- "usedScene": {
- "abilities": ["EntryAbility"],
- "when": "always"
- }
- }
复制代码 调试模式下可能侥幸通过,但正式包会暴露问题,建议开发初期即配置。
- 编码问题:http包默认UTF-8解码,若服务端返回GBK编码(较少见)会出现乱码,需手动指定编码。绝大多数API无此问题。
结语
http包的get/post/put/delete四个方法覆盖了90%的网络请求场景,配合状态枚举和友好错误处理即可构建稳定用法。当项目复杂度提升时,可在http基础上封装ApiClient,统一处理baseUrl、token注入和异常转换。更高级的需求(拦截器、文件上传、请求取消)可迁移至dio。最后再次强调:鸿蒙开发中务必先配置INTERNET权限,否则卡在连接错误上浪费大量调试时间。 |