flutter-http-and-json

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

flutter-http-json-networking

flutter-http-json-networking

Goal

目标

Manages HTTP networking and JSON data handling in Flutter applications. Implements secure, asynchronous REST API calls (GET, POST, PUT, DELETE) using the
http
package. Handles JSON serialization, background parsing via isolates for large datasets, and structured JSON schemas for AI model integrations. Assumes the
http
package is added to
pubspec.yaml
and the environment supports Dart 3 pattern matching and null safety.
管理Flutter应用中的HTTP网络请求和JSON数据处理。使用
http
包实现安全的异步REST API调用(GET、POST、PUT、DELETE)。支持JSON序列化、针对大型数据集的isolate后台解析,以及用于AI模型集成的结构化JSON schema。假设
http
包已添加到
pubspec.yaml
中,且运行环境支持Dart 3模式匹配和空安全。

Decision Logic

决策逻辑

When implementing JSON parsing and serialization, evaluate the following decision tree:
  1. Payload Size:
    • If the JSON payload is small, parse synchronously on the main thread.
    • If the JSON payload is large (takes >16ms to parse), use background parsing via
      compute()
      to avoid UI jank.
  2. Model Complexity:
    • If the data model is simple or a quick prototype, use manual serialization (
      dart:convert
      ).
    • If the data model is highly nested or part of a large production app, STOP AND ASK THE USER: "Should we configure
      json_serializable
      and
      build_runner
      for automated code generation?"
实现JSON解析和序列化时,请参考以下决策树:
  1. 负载大小:
    • 如果JSON负载很小,在主线程上同步解析。
    • 如果JSON负载很大(解析耗时>16ms),通过
      compute()
      使用后台解析避免UI卡顿。
  2. 模型复杂度:
    • 如果数据模型简单或者是快速原型,使用手动序列化(
      dart:convert
      )。
    • 如果数据模型嵌套层级很深,或者属于大型生产应用,请暂停并询问用户:"我们是否需要配置
      json_serializable
      build_runner
      来实现自动化代码生成?"

Instructions

使用说明

1. Configure Platform Permissions

1. 配置平台权限

Before making network requests, ensure the target platforms have the required internet permissions.
Android (
android/app/src/main/AndroidManifest.xml
):
xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- Required to fetch data from the internet. -->
    <uses-permission android:name="android.permission.INTERNET" />
    <application ...>
</manifest>
macOS (
macos/Runner/DebugProfile.entitlements
and
Release.entitlements
):
xml
<dict>
    <!-- Required to fetch data from the internet. -->
    <key>com.apple.security.network.client</key>
    <true/>
</dict>
发起网络请求前,请确保目标平台已配置所需的网络权限。
Android(
android/app/src/main/AndroidManifest.xml
):
xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- Required to fetch data from the internet. -->
    <uses-permission android:name="android.permission.INTERNET" />
    <application ...>
</manifest>
macOS(
macos/Runner/DebugProfile.entitlements
Release.entitlements
):
xml
<dict>
    <!-- Required to fetch data from the internet. -->
    <key>com.apple.security.network.client</key>
    <true/>
</dict>

2. Define the JSON Data Model

2. 定义JSON数据模型

Create a strongly typed Dart class to represent the JSON data. Use factory constructors for deserialization and a
toJson
method for serialization.
dart
import 'dart:convert';

class ItemModel {
  final int id;
  final String title;

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

  // Deserialize using Dart 3 pattern matching
  factory ItemModel.fromJson(Map<String, dynamic> json) {
    return switch (json) {
      {'id': int id, 'title': String title} => ItemModel(id: id, title: title),
      _ => throw const FormatException('Failed to parse ItemModel.'),
    };
  }

  // Serialize to JSON
  Map<String, dynamic> toJson() => {
        'id': id,
        'title': title,
      };
}
创建强类型Dart类来表示JSON数据。使用工厂构造函数实现反序列化,通过
toJson
方法实现序列化。
dart
import 'dart:convert';

class ItemModel {
  final int id;
  final String title;

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

  // Deserialize using Dart 3 pattern matching
  factory ItemModel.fromJson(Map<String, dynamic> json) {
    return switch (json) {
      {'id': int id, 'title': String title} => ItemModel(id: id, title: title),
      _ => throw const FormatException('Failed to parse ItemModel.'),
    };
  }

  // Serialize to JSON
  Map<String, dynamic> toJson() => {
        'id': id,
        'title': title,
      };
}

3. Implement HTTP Operations (CRUD)

3. 实现HTTP操作(CRUD)

Use the
http
package to perform network requests. Always use
Uri.https
for safe URL encoding. Validate the status code and throw exceptions on failure.
dart
import 'dart:convert';
import 'package:http/http.dart' as http;

class ApiService {
  final http.Client client;
  ApiService(this.client);

  // GET Request
  Future<ItemModel> fetchItem(int id) async {
    final uri = Uri.https('api.example.com', '/items/$id');
    final response = await client.get(uri);

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

  // POST Request
  Future<ItemModel> createItem(String title) async {
    final uri = Uri.https('api.example.com', '/items');
    final response = await client.post(
      uri,
      headers: <String, String>{'Content-Type': 'application/json; charset=UTF-8'},
      body: jsonEncode(<String, String>{'title': title}),
    );

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

  // DELETE Request
  Future<void> deleteItem(int id) async {
    final uri = Uri.https('api.example.com', '/items/$id');
    final response = await client.delete(
      uri,
      headers: <String, String>{'Content-Type': 'application/json; charset=UTF-8'},
    );

    if (response.statusCode != 200) {
      throw Exception('Failed to delete item: ${response.statusCode}');
    }
  }
}
使用
http
包执行网络请求。始终使用
Uri.https
实现安全的URL编码。校验状态码,请求失败时抛出异常。
dart
import 'dart:convert';
import 'package:http/http.dart' as http;

class ApiService {
  final http.Client client;
  ApiService(this.client);

  // GET Request
  Future<ItemModel> fetchItem(int id) async {
    final uri = Uri.https('api.example.com', '/items/$id');
    final response = await client.get(uri);

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

  // POST Request
  Future<ItemModel> createItem(String title) async {
    final uri = Uri.https('api.example.com', '/items');
    final response = await client.post(
      uri,
      headers: <String, String>{'Content-Type': 'application/json; charset=UTF-8'},
      body: jsonEncode(<String, String>{'title': title}),
    );

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

  // DELETE Request
  Future<void> deleteItem(int id) async {
    final uri = Uri.https('api.example.com', '/items/$id');
    final response = await client.delete(
      uri,
      headers: <String, String>{'Content-Type': 'application/json; charset=UTF-8'},
    );

    if (response.statusCode != 200) {
      throw Exception('Failed to delete item: ${response.statusCode}');
    }
  }
}

4. Implement Background Parsing for Large JSON Arrays

4. 为大型JSON数组实现后台解析

If fetching a large list of objects, move the JSON decoding and mapping to a separate isolate using
compute()
.
dart
import 'package:flutter/foundation.dart';

// Top-level function required for compute()
List<ItemModel> parseItems(String responseBody) {
  final parsed = (jsonDecode(responseBody) as List<Object?>).cast<Map<String, Object?>>();
  return parsed.map<ItemModel>(ItemModel.fromJson).toList();
}

Future<List<ItemModel>> fetchLargeItemList(http.Client client) async {
  final uri = Uri.https('api.example.com', '/items');
  final response = await client.get(uri);

  if (response.statusCode == 200) {
    // Run parseItems in a separate isolate
    return compute(parseItems, response.body);
  } else {
    throw Exception('Failed to load items');
  }
}
如果要获取大量对象列表,使用
compute()
将JSON解码和映射逻辑移到单独的isolate中执行。
dart
import 'package:flutter/foundation.dart';

// Top-level function required for compute()
List<ItemModel> parseItems(String responseBody) {
  final parsed = (jsonDecode(responseBody) as List<Object?>).cast<Map<String, Object?>>();
  return parsed.map<ItemModel>(ItemModel.fromJson).toList();
}

Future<List<ItemModel>> fetchLargeItemList(http.Client client) async {
  final uri = Uri.https('api.example.com', '/items');
  final response = await client.get(uri);

  if (response.statusCode == 200) {
    // Run parseItems in a separate isolate
    return compute(parseItems, response.body);
  } else {
    throw Exception('Failed to load items');
  }
}

5. Define Structured JSON Output for AI Models

5. 为AI模型定义结构化JSON输出

When integrating LLMs (like Gemini), enforce reliable JSON output by passing a strict schema in the generation configuration and system instructions.
dart
import 'package:firebase_vertexai/firebase_vertexai.dart';

// Define the expected JSON schema
final _responseSchema = Schema(
  SchemaType.object,
  properties: {
    'width': Schema(SchemaType.integer),
    'height': Schema(SchemaType.integer),
    'items': Schema(
      SchemaType.array,
      items: Schema(
        SchemaType.object,
        properties: {
          'id': Schema(SchemaType.integer),
          'name': Schema(SchemaType.string),
        },
      ),
    ),
  },
);

// Initialize the model with the schema
final model = FirebaseAI.googleAI().generativeModel(
  model: 'gemini-2.5-pro',
  generationConfig: GenerationConfig(
    responseMimeType: 'application/json',
    responseSchema: _responseSchema,
  ),
);

Future<Map<String, dynamic>> analyzeData(String prompt) async {
  final content = [Content.text(prompt)];
  final response = await model.generateContent(content);
  
  // Safely decode the guaranteed JSON response
  return jsonDecode(response.text!) as Map<String, dynamic>;
}
集成LLM(例如Gemini)时,通过在生成配置和系统指令中传入严格的schema来确保JSON输出稳定可靠。
dart
import 'package:firebase_vertexai/firebase_vertexai.dart';

// Define the expected JSON schema
final _responseSchema = Schema(
  SchemaType.object,
  properties: {
    'width': Schema(SchemaType.integer),
    'height': Schema(SchemaType.integer),
    'items': Schema(
      SchemaType.array,
      items: Schema(
        SchemaType.object,
        properties: {
          'id': Schema(SchemaType.integer),
          'name': Schema(SchemaType.string),
        },
      ),
    ),
  },
);

// Initialize the model with the schema
final model = FirebaseAI.googleAI().generativeModel(
  model: 'gemini-2.5-pro',
  generationConfig: GenerationConfig(
    responseMimeType: 'application/json',
    responseSchema: _responseSchema,
  ),
);

Future<Map<String, dynamic>> analyzeData(String prompt) async {
  final content = [Content.text(prompt)];
  final response = await model.generateContent(content);
  
  // Safely decode the guaranteed JSON response
  return jsonDecode(response.text!) as Map<String, dynamic>;
}

Constraints

约束条件

  • Immutable URL Construction: Always use
    Uri.https()
    or
    Uri.parse()
    to build URLs. Never use raw string concatenation for endpoints with query parameters.
  • Error Handling: Never return
    null
    on a failed network request. Always throw an
    Exception
    or a custom error class so the UI (e.g.,
    FutureBuilder
    ) can catch and display the error state via
    snapshot.hasError
    .
  • Status Code Validation: Always validate
    response.statusCode
    . Use
    200
    for successful GET/PUT/DELETE and
    201
    for successful POST.
  • Library Restriction: Do not use
    dart:io
    HttpClient
    directly for standard cross-platform networking. Always use the
    http
    package to ensure web compatibility.
  • Isolate Communication: When using
    compute()
    , ensure the parsing function is a top-level function or a static method, and only pass primitive values or simple objects (like
    String
    response bodies) across the isolate boundary. Do not pass
    http.Response
    objects.
  • URL构造不可变: 始终使用
    Uri.https()
    Uri.parse()
    构建URL。不要对带查询参数的接口使用原始字符串拼接。
  • 错误处理: 网络请求失败时永远不要返回
    null
    。始终抛出
    Exception
    或自定义错误类,以便UI(例如
    FutureBuilder
    )可以通过
    snapshot.hasError
    捕获并展示错误状态。
  • 状态码校验: 始终校验
    response.statusCode
    。GET/PUT/DELETE请求成功使用200状态码,POST请求成功使用201状态码。
  • 库使用限制: 标准跨平台网络请求不要直接使用
    dart:io
    HttpClient
    。始终使用
    http
    包以保证web端兼容性。
  • Isolate通信: 使用
    compute()
    时,确保解析函数是顶层函数或者静态方法,并且仅跨isolate边界传递基础类型值或简单对象(例如
    String
    类型的响应体)。不要传递
    http.Response
    对象。