flutter-handling-http-and-json

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Handling HTTP and JSON

处理HTTP与JSON

Contents

目录

Core Guidelines

核心准则

  • Enforce HTTPS: iOS and Android disable cleartext (HTTP) connections by default. Always use HTTPS endpoints. If HTTP is strictly required for debugging, configure
    network_security_config.xml
    (Android) and
    NSAppTransportSecurity
    (iOS).
  • Construct URIs Safely: Always use
    Uri.https(authority, unencodedPath, [queryParameters])
    to safely build URLs. This handles encoding and formatting reliably, preventing string concatenation errors.
  • Handle Status Codes: Always validate the
    http.Response.statusCode
    . Treat
    200
    (OK) and
    201
    (Created) as success. Throw explicit exceptions for other codes (do not return
    null
    ).
  • Prevent UI Jank: Move expensive JSON parsing operations (taking >16ms) to a background isolate using the
    compute()
    function.
  • Structured AI Output: When integrating LLMs, enforce reliable JSON output by specifying a strict JSON schema in the system prompt and setting the response MIME type to
    application/json
    .
  • 强制使用HTTPS:iOS和Android默认禁用明文(HTTP)连接。请始终使用HTTPS端点。如果调试时确实需要使用HTTP,请配置Android的
    network_security_config.xml
    和iOS的
    NSAppTransportSecurity
  • 安全构造URI:请始终使用
    Uri.https(authority, unencodedPath, [queryParameters])
    来安全构建URL。它能可靠地处理编码和格式化,避免字符串拼接错误。
  • 处理状态码:务必验证
    http.Response.statusCode
    。将
    200
    (成功)和
    201
    (已创建)视为请求成功。对于其他状态码,抛出明确的异常(不要返回
    null
    )。
  • 避免UI卡顿:使用
    compute()
    函数将耗时的JSON解析操作(超过16ms)移至后台隔离线程执行。
  • 结构化AI输出:集成大语言模型(LLM)时,通过在系统提示中指定严格的JSON模式,并将响应MIME类型设置为
    application/json
    ,确保输出可靠的JSON格式。

Workflow: Executing HTTP Operations

工作流:执行HTTP操作

Use this workflow to implement network requests using the
http
package.
Task Progress:
  • Add the
    http
    package to
    pubspec.yaml
    .
  • Configure platform permissions (Internet permission in
    AndroidManifest.xml
    and macOS
    .entitlements
    ).
  • Construct the target
    Uri
    .
  • Execute the HTTP method.
  • Validate the response and parse the JSON payload.
Conditional Implementation:
  • If fetching data (GET): Use
    http.get(uri)
    .
  • If sending new data (POST): Use
    http.post(uri, headers: {...}, body: jsonEncode(data))
    . Ensure
    Content-Type
    is
    application/json; charset=UTF-8
    .
  • If updating data (PUT): Use
    http.put(uri, headers: {...}, body: jsonEncode(data))
    .
  • If deleting data (DELETE): Use
    http.delete(uri, headers: {...})
    .
Feedback Loop: Validation & Error Handling
  1. Run the HTTP request.
  2. Check
    response.statusCode
    .
  3. If
    200
    or
    201
    , call
    jsonDecode(response.body)
    and map to a Dart object.
  4. If any other code, throw an
    Exception('Failed to load/update/delete resource')
    .
  5. Review errors -> fix endpoint, headers, or payload structure.
使用此工作流,基于
http
包实现网络请求。
任务进度:
  • pubspec.yaml
    中添加
    http
    包。
  • 配置平台权限(AndroidManifest.xml中的互联网权限,以及macOS的
    .entitlements
    权限)。
  • 构造目标
    Uri
  • 执行HTTP方法。
  • 验证响应并解析JSON负载。
条件化实现:
  • 如果是获取数据(GET):使用
    http.get(uri)
  • 如果是发送新数据(POST):使用
    http.post(uri, headers: {...}, body: jsonEncode(data))
    。确保
    Content-Type
    设置为
    application/json; charset=UTF-8
  • 如果是更新数据(PUT):使用
    http.put(uri, headers: {...}, body: jsonEncode(data))
  • 如果是删除数据(DELETE):使用
    http.delete(uri, headers: {...})
反馈循环:验证与错误处理
  1. 执行HTTP请求。
  2. 检查
    response.statusCode
  3. 如果状态码为200或201,调用
    jsonDecode(response.body)
    并映射为Dart对象。
  4. 如果是其他状态码,抛出
    Exception('Failed to load/update/delete resource')
    (加载/更新/删除资源失败)。
  5. 排查错误 -> 修复端点、请求头或负载结构。

Workflow: Implementing JSON Serialization

工作流:实现JSON序列化

Choose the serialization strategy based on project complexity.
Conditional Implementation:
  • If building a small prototype or simple models: Use manual serialization with
    dart:convert
    .
  • If building a production app with complex/nested models: Use code generation with
    json_serializable
    .
根据项目复杂度选择序列化策略。
条件化实现:
  • 如果是构建小型原型或简单模型:使用
    dart:convert
    进行手动序列化。
  • 如果是构建包含复杂/嵌套模型的生产应用:使用
    json_serializable
    进行代码生成。

Manual Serialization Setup

手动序列化设置

Task Progress:
  • Import
    dart:convert
    .
  • Define the Model class with
    final
    properties.
  • Implement a
    factory Model.fromJson(Map<String, dynamic> json)
    constructor.
  • Implement a
    Map<String, dynamic> toJson()
    method.
任务进度:
  • 导入
    dart:convert
  • 定义带有
    final
    属性的Model类。
  • 实现
    factory Model.fromJson(Map<String, dynamic> json)
    构造函数。
  • 实现
    Map<String, dynamic> toJson()
    方法。

Code Generation Setup (
json_serializable
)

代码生成设置(
json_serializable

Task Progress:
  • Add dependencies:
    flutter pub add json_annotation
    and
    flutter pub add -d build_runner json_serializable
    .
  • Import
    json_annotation.dart
    in the model file.
  • Add the
    part 'model_name.g.dart';
    directive.
  • Annotate the class with
    @JsonSerializable()
    . Use
    explicitToJson: true
    if the class contains nested models.
  • Define the
    fromJson
    factory and
    toJson
    method delegating to the generated functions.
  • Run the generator:
    dart run build_runner build --delete-conflicting-outputs
    .
任务进度:
  • 添加依赖:
    flutter pub add json_annotation
    flutter pub add -d build_runner json_serializable
  • 在模型文件中导入
    json_annotation.dart
  • 添加
    part 'model_name.g.dart';
    指令。
  • 使用
    @JsonSerializable()
    注解类。如果类包含嵌套模型,设置
    explicitToJson: true
  • 定义
    fromJson
    工厂方法和
    toJson
    方法,委托给生成的函数。
  • 运行生成器:
    dart run build_runner build --delete-conflicting-outputs

Workflow: Parsing Large JSON in the Background

工作流:在后台解析大型JSON

Use this workflow to prevent frame drops when parsing large JSON payloads (e.g., lists of 1000+ items).
Task Progress:
  • Create a top-level or static function that takes a
    String
    (the response body) and returns the parsed Dart object (e.g.,
    List<Model>
    ).
  • Inside the function, call
    jsonDecode
    and map the results to the Model class.
  • In the HTTP fetch method, pass the top-level parsing function and the
    response.body
    to Flutter's
    compute()
    function.
当解析大型JSON负载(例如包含1000+条目的列表)时,使用此工作流防止界面掉帧。
任务进度:
  • 创建一个顶级或静态函数,接收
    String
    类型的响应体,并返回解析后的Dart对象(例如
    List<Model>
    )。
  • 在函数内部,调用
    jsonDecode
    并将结果映射到Model类。
  • 在HTTP获取方法中,将顶级解析函数和
    response.body
    传递给Flutter的
    compute()
    函数。

Examples

示例

Example 1: HTTP GET with Manual Serialization

示例1:手动序列化的HTTP GET请求

dart
import 'dart:convert';
import 'package:http/http.dart' as http;

class Album {
  final int id;
  final String title;

  const Album({required this.id, required this.title});

  factory Album.fromJson(Map<String, dynamic> json) {
    return Album(
      id: json['id'] as int,
      title: json['title'] as String,
    );
  }
}

Future<Album> fetchAlbum() async {
  final uri = Uri.https('jsonplaceholder.typicode.com', '/albums/1');
  final response = await http.get(uri);

  if (response.statusCode == 200) {
    return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
  } else {
    throw Exception('Failed to load album');
  }
}
dart
import 'dart:convert';
import 'package:http/http.dart' as http;

class Album {
  final int id;
  final String title;

  const Album({required this.id, required this.title});

  factory Album.fromJson(Map<String, dynamic> json) {
    return Album(
      id: json['id'] as int,
      title: json['title'] as String,
    );
  }
}

Future<Album> fetchAlbum() async {
  final uri = Uri.https('jsonplaceholder.typicode.com', '/albums/1');
  final response = await http.get(uri);

  if (response.statusCode == 200) {
    return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
  } else {
    throw Exception('Failed to load album');
  }
}

Example 2: HTTP POST Request

示例2:HTTP POST请求

dart
Future<Album> createAlbum(String title) async {
  final uri = Uri.https('jsonplaceholder.typicode.com', '/albums');
  final response = await http.post(
    uri,
    headers: <String, String>{
      'Content-Type': 'application/json; charset=UTF-8',
    },
    body: jsonEncode(<String, String>{'title': title}),
  );

  if (response.statusCode == 201) {
    return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
  } else {
    throw Exception('Failed to create album.');
  }
}
dart
Future<Album> createAlbum(String title) async {
  final uri = Uri.https('jsonplaceholder.typicode.com', '/albums');
  final response = await http.post(
    uri,
    headers: <String, String>{
      'Content-Type': 'application/json; charset=UTF-8',
    },
    body: jsonEncode(<String, String>{'title': title}),
  );

  if (response.statusCode == 201) {
    return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
  } else {
    throw Exception('Failed to create album.');
  }
}

Example 3: Background Parsing with
compute

示例3:使用
compute
进行后台解析

dart
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;

// 1. Top-level function for parsing
List<Photo> parsePhotos(String responseBody) {
  final parsed = (jsonDecode(responseBody) as List<Object?>)
      .cast<Map<String, Object?>>();
  return parsed.map<Photo>(Photo.fromJson).toList();
}

// 2. Fetch function using compute
Future<List<Photo>> fetchPhotos(http.Client client) async {
  final uri = Uri.https('jsonplaceholder.typicode.com', '/photos');
  final response = await client.get(uri);

  if (response.statusCode == 200) {
    // Run parsePhotos in a separate isolate
    return compute(parsePhotos, response.body);
  } else {
    throw Exception('Failed to load photos');
  }
}
dart
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;

// 1. 用于解析的顶级函数
List<Photo> parsePhotos(String responseBody) {
  final parsed = (jsonDecode(responseBody) as List<Object?>)
      .cast<Map<String, Object?>>();
  return parsed.map<Photo>(Photo.fromJson).toList();
}

// 2. 使用compute的获取函数
Future<List<Photo>> fetchPhotos(http.Client client) async {
  final uri = Uri.https('jsonplaceholder.typicode.com', '/photos');
  final response = await client.get(uri);

  if (response.statusCode == 200) {
    // 在独立隔离线程中运行parsePhotos
    return compute(parsePhotos, response.body);
  } else {
    throw Exception('Failed to load photos');
  }
}

Example 4: Code Generation (
json_serializable
)

示例4:代码生成(
json_serializable

dart
import 'package:json_annotation/json_annotation.dart';

part 'user.g.dart';

(explicitToJson: true)
class User {
  final String name;
  
  (name: 'registration_date_millis')
  final int registrationDateMillis;

  User(this.name, this.registrationDateMillis);

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}
dart
import 'package:json_annotation/json_annotation.dart';

part 'user.g.dart';

(explicitToJson: true)
class User {
  final String name;
  
  (name: 'registration_date_millis')
  final int registrationDateMillis;

  User(this.name, this.registrationDateMillis);

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}