04. Flutter 鸿蒙实战 04封装 ApiClient请求 JSON、字节流和错误提示不用简单 http 包直接基于 dart:io 封装统一网络层。网络层为什么单独封装项目的网络请求没有散落在页面里而是统一放在lib/core/network/api_client.dart。这个文件超过一万行字节数承担 JSON 请求、字节流请求、multipart 上传、token 头、错误转换等公共能力。对于 Flutter 鸿蒙项目这样做非常有必要移动端网络错误、后端状态码、文件上传进度、视频字节下载都需要一致处理。基础配置来自AppConfigabstractfinalclassAppConfig{staticconstapiBaseUrlString.fromEnvironment(API_BASE_URL,defaultValue:https://talking-album.zifeiyu.site,);}这里保留了--dart-defineAPI_BASE_URL...的能力。开发时可以指向本机后端正式包默认走线上服务器。项目变更记录里也记录过一次真实问题默认地址如果是127.0.0.1安装到手机后会请求手机本机不会请求电脑。JSON 请求ApiClient 暴露了getJson、getJsonList、postJson、patchJson、deleteJsonFutureMapString,dynamicgetJson(Stringpath)async{finalresultawait_send(GET,path);if(resultis!MapString,dynamic){throwApiException(_invalidResponseMessage,path:path);}returnresult;}FutureListdynamicgetJsonList(Stringpath)async{finalresultawait_send(GET,path);if(resultis!Listdynamic){throwApiException(_invalidResponseMessage,path:path);}returnresult;}这里强制校验返回结构。接口约定返回对象就必须是对象返回数组就必须是数组。否则页面拿到错误结构后再崩溃定位会更麻烦。Token 注入ApiClient持有accessToken。登录成功后AuthController会把 token 写进去_apiClient.accessTokenresult.token;请求时自动设置 Authorizationif(accessTokencasefinaltoken?){request.headers.set(HttpHeaders.authorizationHeader,Bearer$token);}这样 Repository 不需要关心 token。登录、故事、上传、个人资料等模块都通过同一个 ApiClient 请求权限头由网络层统一处理。字节流请求项目里照片、头像、活化视频都不是普通 JSON。ApiClient提供getBytesFutureUint8ListgetBytes(Stringpath)_requestBytes(GET,path);FutureUint8List_requestBytes(Stringmethod,Stringpath)async{return_requestBytesUrl(method,Uri.parse(${AppConfig.apiBaseUrl}$path),path,authorized:true,);}故事列表展示缩略图、地图 marker 展示照片、故事详情加载原图都会用到字节流。把字节请求封装起来可以复用 token、状态码判断和错误转换。Multipart 上传照片上传和头像上传都需要 multipart。项目没有把上传逻辑塞到页面里而是在 ApiClient 里实现了通用方法FutureMapString,dynamicuploadMultipartBytes({requiredStringpath,requiredStringfieldName,requiredUint8Listbytes,requiredStringfilename,requiredStringmimeType,voidFunction(double)?onProgress,})async{finalboundarytalking-album-${DateTime.now().microsecondsSinceEpoch};finalprefixutf8.encode(--$boundary\r\nContent-Disposition: form-data; name$fieldName; filename$filename\r\nContent-Type:$mimeType\r\n\r\n,);finalsuffixutf8.encode(\r\n--$boundary--\r\n);finaltotalprefix.lengthbytes.lengthsuffix.length;}上传进度通过onProgress回调给页面。页面只负责显示进度条不需要理解 multipart 边界和分片写入。用户可读错误网络层定义了常见错误文案staticconst_networkMessage未连接网络请确认网络是否正常;staticconst_timeoutMessage网络连接超时请稍后重试;staticconst_requestFailedMessage操作失败请稍后重试;staticconst_invalidResponseMessage服务器返回数据异常请稍后重试;这类文案在移动端比原始异常更重要。用户看到SocketException没有意义看到“未连接网络”才知道该检查网络。扩展拆解ApiClient 的可复用价值ApiClient最大的价值是把所有请求风格统一。登录、注册、故事、地图、上传、头像、视频都走同一个网络入口。后续如果要加请求日志、统一超时、统一重试、统一 token 过期处理只需要改网络层不需要逐个页面修改。项目里的请求类型可以分成三类JSON 请求登录、注册、故事列表、地图地点、纪念日 字节请求照片原图、头像、地图缩略图、活化视频 Multipart照片上传、头像上传这三类请求都已经在 ApiClient 中有入口。页面不直接操作 HttpClient而是调用 RepositoryRepository 再调用 ApiClient网络细节统一收口到基础层。默认 API 地址的坑项目记录里修过“重装后显示网络未连接”原因是默认 API 地址曾经指向127.0.0.1。手机里的127.0.0.1是手机自己不是电脑。后来默认值改成线上服务开发调试时通过--dart-define覆盖这个做法更适合交付安装包。认 API 地址的坑项目记录里修过“重装后显示网络未连接”原因是默认 API 地址曾经指向127.0.0.1。手机里的127.0.0.1是手机自己不是电脑。后来默认值改成线上服务开发调试时通过--dart-define覆盖这个做法更适合交付安装包。