ToolkitX
知识库工具箱

网络请求

http, dio, JSON 解析

20min·进阶

01. http 包发起请求

Flutter 最基础的网络请求用 http 包,http.get 发 GET,http.post 发 POST。返回的 Response 有 statusCode 和 body(字符串)。需要反序列化 JSON 用 dart:convert 的 jsonDecode。虽然简单但也够用,小项目完全可以不引入第三方网络库。
dart
import 'package:http/http.dart' as http;
import 'dart:convert';

Future<List<User>> fetchUsers() async {
  final response = await http.get(
    Uri.parse('https://api.example.com/users'),
    headers: {'Authorization': 'Bearer $token'},
  );
  
  if (response.statusCode == 200) {
    final List<dynamic> jsonList = jsonDecode(response.body);
    return jsonList.map((json) => User.fromJson(json)).toList();
  } else {
    throw Exception('请求失败: ${response.statusCode}');
  }
}

Future<void> createUser(User user) async {
  final response = await http.post(
    Uri.parse('https://api.example.com/users'),
    headers: {'Content-Type': 'application/json'},
    body: jsonEncode(user.toJson()),
  );
  
  if (response.statusCode != 201) {
    throw Exception('创建失败');
  }
}
jsonDecode 默认返回 dynamic,不安全。用 Map<String, dynamic> 显式声明类型更好。

02. JSON 序列化

Dart 没有像 Gson 那样的反射序列化,需要手写 fromJson 和 toJson 方法。json_serializable 库用代码生成自动写序列化代码,需要运行 build_runner。手动写虽然麻烦但简单可控,小项目手写够用。fromJson 是工厂构造函数,toJson 返回 Map。
dart
class User {
  final int id;
  final String name;
  final String? email;

  User({required this.id, required this.name, this.email});

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'] as int,
      name: json['name'] as String,
      email: json['email'] as String?,
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'name': name,
      'email': email,
    };
  }
}

// 使用 json_serializable 自动生成
// @JsonSerializable()
// class User {
//   final int id;
//   final String name;
//   factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
//   Map<String, dynamic> toJson() => _$UserToJson(this);
// }
json_serializable 需要额外配置 build_runner,虽然省代码但多了构建步骤,项目初期手动写更简单。

03. Dio 高级网络库

Dio 是 Flutter 最流行的第三方网络库,比 http 包功能强大得多。支持拦截器(请求/响应/异常三方拦截)、拦截器链、取消请求、超时设置、文件下载上传进度、BaseUrl 配置、Cookie 管理等。拦截器最适合做 token 刷新和日志记录。
dart
import 'package:dio/dio.dart';

// 创建实例并配置
final dio = Dio(BaseOptions(
  baseUrl: 'https://api.example.com',
  connectTimeout: Duration(seconds: 5),
  receiveTimeout: Duration(seconds: 3),
  headers: {'Content-Type': 'application/json'},
));

// 添加拦截器
dio.interceptors.add(InterceptorsWrapper(
  onRequest: (options, handler) {
    options.headers['Authorization'] = 'Bearer $token';
    print('请求: ${options.method} ${options.path}');
    handler.next(options);
  },
  onResponse: (response, handler) {
    print('响应: ${response.statusCode}');
    handler.next(response);
  },
  onError: (error, handler) {
    if (error.response?.statusCode == 401) {
      // token 过期,刷新 token 后重试
      refreshToken().then((_) => handler.resolve(retryRequest(error.requestOptions)));
      return;
    }
    handler.next(error);
  },
));

// 使用
final response = await dio.get('/users', queryParameters: {'page': 1});
final data = response.data;

// 表单上传
final formData = FormData.fromMap({
  'file': await MultipartFile.fromFile('path/to/file.jpg'),
  'name': 'avatar',
});
await dio.post('/upload', data: formData);
Dio 拦截器链按添加顺序执行,Request 拦截器正序,Response 和 Error 拦截器倒序。

04. 网络异常处理和加载状态

网络请求必须处理各种异常:超时、无网络、服务器错误、JSON 解析失败。用 try-catch 包裹 await 调用,分别 catch DioException 和通用 Exception。UI 层通常有三种状态:loading 显示加载动画、error 显示错误重试按钮、loaded 显示数据。
dart
class UserProvider extends ChangeNotifier {
  List<User> _users = [];
  bool _isLoading = false;
  String? _error;

  List<User> get users => _users;
  bool get isLoading => _isLoading;
  String? get error => _error;

  Future<void> fetchUsers() async {
    _isLoading = true;
    _error = null;
    notifyListeners();

    try {
      final response = await dio.get('/users');
      _users = (response.data as List)
          .map((json) => User.fromJson(json))
          .toList();
    } on DioException catch (e) {
      switch (e.type) {
        case DioExceptionType.connectionTimeout:
          _error = '连接超时,请检查网络';
          break;
        case DioExceptionType.receiveTimeout:
          _error = '服务器响应超时';
          break;
        case DioExceptionType.badResponse:
          _error = '服务器错误: ${e.response?.statusCode}';
          break;
        default:
          _error = '网络异常: ${e.message}';
      }
    } catch (e) {
      _error = '数据解析失败';
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }
}
网络状态的 loading/error/data 三态管理是几乎所有 App 页面都需要的,可以封装成通用组件。

05. WebSocket 实时通信

WebSocket 适合需要服务端主动推送的场景:即时通讯、股票行情、在线协作。dart:io 内置 WebSocket,连接后通过 Stream 监听消息。web_socket_channel 包提供了更友好的 API。onMessage 处理收到的消息,sink.add 发送消息。记得在 dispose 时关闭连接。
dart
import 'package:web_socket_channel/web_socket_channel.dart';

class ChatService {
  late WebSocketChannel _channel;
  
  void connect(String token) {
    _channel = WebSocketChannel.connect(
      Uri.parse('wss://chat.example.com/ws?token=$token'),
    );
  }
  
  Stream get messages => _channel.stream;
  
  void sendMessage(String text) {
    _channel.sink.add(jsonEncode({
      'type': 'message',
      'content': text,
    }));
  }
  
  void disconnect() {
    _channel.sink.close();
  }
}

// Widget 中使用
class ChatPage extends StatefulWidget {
  @override
  _ChatPageState createState() => _ChatPageState();
}

class _ChatPageState extends State<ChatPage> {
  final _service = ChatService();

  @override
  void initState() {
    super.initState();
    _service.connect('token');
  }

  @override
  Widget build(BuildContext context) {
    return StreamBuilder(
      stream: _service.messages,
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return Text('收到: ${snapshot.data}');
        }
        return Text('等待消息...');
      },
    );
  }

  @override
  void dispose() {
    _service.disconnect();
    super.dispose();
  }
}
客户端 WebSocket 注意断线重连和心跳保活,生产环境要加上重连逻辑。

知识测验

1/4正确 0

Flutter 中 http 包获取数据后如何解析 JSON?