Flutter之数据调用和后端

📚 目录

  1. 概述
  2. 网络请求(HTTP)
  3. JSON 序列化和反序列化
  4. 数据持久化
  5. 状态管理与数据
  6. 错误处理
  7. 缓存策略
  8. 与后端 API 交互
  9. 最佳实践
  10. 常见问题

概述

Flutter 应用需要与后端服务交互来获取和发送数据。主要涉及:

  • 网络请求:使用 HTTP 协议与服务器通信
  • 数据序列化:将 JSON 数据转换为 Dart 对象
  • 数据持久化:本地存储数据(SharedPreferences、SQLite、文件等)
  • 状态管理:管理异步数据的状态
  • 错误处理:处理网络错误和异常

网络请求(HTTP)

1. 使用 http 包

安装:

dependencies:
  http: ^1.1.0

基本用法:

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

// GET 请求
Future<void> fetchData() async {
  final response = await http.get(
    Uri.parse('https://api.example.com/data'),
  );
  
  if (response.statusCode == 200) {
    // 请求成功
    final data = jsonDecode(response.body);
    print(data);
  } else {
    // 请求失败
    print('请求失败: ${response.statusCode}');
  }
}

// POST 请求
Future<void> sendData() async {
  final response = await http.post(
    Uri.parse('https://api.example.com/data'),
    headers: {'Content-Type': 'application/json'},
    body: jsonEncode({
      'name': 'John',
      'email': 'john@example.com',
    }),
  );
  
  if (response.statusCode == 201) {
    print('数据发送成功');
  }
}

2. 完整的 HTTP 服务类

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

class ApiService {
  static const String baseUrl = 'https://api.example.com';
  
  // GET 请求
  static Future<Map<String, dynamic>> get(String endpoint) async {
    try {
      final response = await http.get(
        Uri.parse('$baseUrl$endpoint'),
        headers: {
          'Content-Type': 'application/json',
          'Accept': 'application/json',
        },
      );
      
      return _handleResponse(response);
    } catch (e) {
      throw Exception('网络请求失败: $e');
    }
  }
  
  // POST 请求
  static Future<Map<String, dynamic>> post(
    String endpoint,
    Map<String, dynamic> data,
  ) async {
    try {
      final response = await http.post(
        Uri.parse('$baseUrl$endpoint'),
        headers: {
          'Content-Type': 'application/json',
          'Accept': 'application/json',
        },
        body: jsonEncode(data),
      );
      
      return _handleResponse(response);
    } catch (e) {
      throw Exception('网络请求失败: $e');
    }
  }
  
  // PUT 请求
  static Future<Map<String, dynamic>> put(
    String endpoint,
    Map<String, dynamic> data,
  ) async {
    try {
      final response = await http.put(
        Uri.parse('$baseUrl$endpoint'),
        headers: {
          'Content-Type': 'application/json',
          'Accept': 'application/json',
        },
        body: jsonEncode(data),
      );
      
      return _handleResponse(response);
    } catch (e) {
      throw Exception('网络请求失败: $e');
    }
  }
  
  // DELETE 请求
  static Future<void> delete(String endpoint) async {
    try {
      final response = await http.delete(
        Uri.parse('$baseUrl$endpoint'),
        headers: {
          'Content-Type': 'application/json',
        },
      );
      
      _handleResponse(response);
    } catch (e) {
      throw Exception('网络请求失败: $e');
    }
  }
  
  // 处理响应
  static Map<String, dynamic> _handleResponse(http.Response response) {
    if (response.statusCode >= 200 && response.statusCode < 300) {
      if (response.body.isEmpty) {
        return {};
      }
      return jsonDecode(response.body) as Map<String, dynamic>;
    } else {
      throw HttpException(
        '请求失败: ${response.statusCode}',
        statusCode: response.statusCode,
      );
    }
  }
}

// 自定义异常
class HttpException implements Exception {
  final String message;
  final int? statusCode;
  
  HttpException(this.message, {this.statusCode});
  
  @override
  String toString() => message;
}

3. 带认证的请求

class AuthenticatedApiService {
  static String? _token;
  
  static void setToken(String token) {
    _token = token;
  }
  
  static Map<String, String> _getHeaders() {
    final headers = {
      'Content-Type': 'application/json',
      'Accept': 'application/json',
    };
    
    if (_token != null) {
      headers['Authorization'] = 'Bearer $_token';
    }
    
    return headers;
  }
  
  static Future<Map<String, dynamic>> get(String endpoint) async {
    final response = await http.get(
      Uri.parse('https://api.example.com$endpoint'),
      headers: _getHeaders(),
    );
    
    return _handleResponse(response);
  }
  
  // ... 其他方法类似
}

4. 使用 dio 包(推荐)

dio 是一个功能更强大的 HTTP 客户端库。

安装:

dependencies:
  dio: ^5.4.0

基本用法:

import 'package:dio/dio.dart';

class DioService {
  late Dio _dio;
  
  DioService() {
    _dio = Dio(
      BaseOptions(
        baseUrl: 'https://api.example.com',
        connectTimeout: const Duration(seconds: 5),
        receiveTimeout: const Duration(seconds: 3),
        headers: {
          'Content-Type': 'application/json',
        },
      ),
    );
    
    // 添加拦截器
    _dio.interceptors.add(LogInterceptor(
      requestBody: true,
      responseBody: true,
    ));
  }
  
  // GET 请求
  Future<Response> get(String path, {Map<String, dynamic>? queryParameters}) async {
    try {
      return await _dio.get(path, queryParameters: queryParameters);
    } on DioException catch (e) {
      throw _handleError(e);
    }
  }
  
  // POST 请求
  Future<Response> post(
    String path,
    Map<String, dynamic>? data,
  ) async {
    try {
      return await _dio.post(path, data: data);
    } on DioException catch (e) {
      throw _handleError(e);
    }
  }
  
  // 错误处理
  String _handleError(DioException error) {
    switch (error.type) {
      case DioExceptionType.connectionTimeout:
        return '连接超时';
      case DioExceptionType.sendTimeout:
        return '发送超时';
      case DioExceptionType.receiveTimeout:
        return '接收超时';
      case DioExceptionType.badResponse:
        return '服务器错误: ${error.response?.statusCode}';
      case DioExceptionType.cancel:
        return '请求已取消';
      default:
        return '网络错误: ${error.message}';
    }
  }
}

JSON 序列化和反序列化

1. 手动序列化

class User {
  final int id;
  final String name;
  final String email;
  
  User({
    required this.id,
    required this.name,
    required this.email,
  });
  
  // 从 JSON 创建对象
  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'] as int,
      name: json['name'] as String,
      email: json['email'] as String,
    );
  }
  
  // 转换为 JSON
  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'name': name,
      'email': email,
    };
  }
}

// 使用
void example() {
  // JSON 字符串
  final jsonString = '{"id": 1, "name": "John", "email": "john@example.com"}';
  
  // 解析 JSON
  final json = jsonDecode(jsonString) as Map<String, dynamic>;
  final user = User.fromJson(json);
  
  // 转换为 JSON
  final userJson = user.toJson();
  final jsonString2 = jsonEncode(userJson);
}

2. 使用 json_serializable(推荐)

自动生成序列化代码,减少样板代码。

安装:

dependencies:
  json_annotation: ^4.8.1

dev_dependencies:
  json_serializable: ^6.7.1
  build_runner: ^2.4.7

定义模型:

import 'package:json_annotation/json_annotation.dart';

part 'user.g.dart';

@JsonSerializable()
class User {
  final int id;
  final String name;
  final String email;
  
  User({
    required this.id,
    required this.name,
    required this.email,
  });
  
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}

生成代码:

flutter pub run build_runner build

3. 处理嵌套对象

@JsonSerializable()
class Address {
  final String street;
  final String city;
  
  Address({required this.street, required this.city});
  
  factory Address.fromJson(Map<String, dynamic> json) => _$AddressFromJson(json);
  Map<String, dynamic> toJson() => _$AddressToJson(this);
}

@JsonSerializable()
class User {
  final int id;
  final String name;
  final Address address;
  
  User({
    required this.id,
    required this.name,
    required this.address,
  });
  
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}

4. 处理列表

// 解析 JSON 数组
final jsonString = '''
[
  {"id": 1, "name": "John"},
  {"id": 2, "name": "Jane"}
]
''';

final List<dynamic> jsonList = jsonDecode(jsonString);
final List<User> users = jsonList
    .map((json) => User.fromJson(json as Map<String, dynamic>))
    .toList();

数据持久化

1. SharedPreferences - 键值对存储

安装:

dependencies:
  shared_preferences: ^2.2.2

使用:

import 'package:shared_preferences/shared_preferences.dart';

class PreferencesService {
  static SharedPreferences? _prefs;
  
  // 初始化
  static Future<void> init() async {
    _prefs = await SharedPreferences.getInstance();
  }
  
  // 保存字符串
  static Future<bool> saveString(String key, String value) async {
    return await _prefs?.setString(key, value) ?? false;
  }
  
  // 读取字符串
  static String? getString(String key) {
    return _prefs?.getString(key);
  }
  
  // 保存整数
  static Future<bool> saveInt(String key, int value) async {
    return await _prefs?.setInt(key, value) ?? false;
  }
  
  // 读取整数
  static int? getInt(String key) {
    return _prefs?.getInt(key);
  }
  
  // 保存布尔值
  static Future<bool> saveBool(String key, bool value) async {
    return await _prefs?.setBool(key, value) ?? false;
  }
  
  // 读取布尔值
  static bool? getBool(String key) {
    return _prefs?.getBool(key);
  }
  
  // 删除
  static Future<bool> remove(String key) async {
    return await _prefs?.remove(key) ?? false;
  }
  
  // 清空所有
  static Future<bool> clear() async {
    return await _prefs?.clear() ?? false;
  }
}

// 使用示例
void example() async {
  await PreferencesService.init();
  
  // 保存数据
  await PreferencesService.saveString('username', 'John');
  await PreferencesService.saveInt('age', 25);
  
  // 读取数据
  final username = PreferencesService.getString('username');
  final age = PreferencesService.getInt('age');
}

2. SQLite - 关系型数据库

安装:

dependencies:
  sqflite: ^2.3.0
  path: ^1.8.3

使用:

import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

class DatabaseHelper {
  static final DatabaseHelper instance = DatabaseHelper._init();
  static Database? _database;
  
  DatabaseHelper._init();
  
  Future<Database> get database async {
    if (_database != null) return _database!;
    _database = await _initDB('app.db');
    return _database!;
  }
  
  Future<Database> _initDB(String filePath) async {
    final dbPath = await getDatabasesPath();
    final path = join(dbPath, filePath);
    
    return await openDatabase(
      path,
      version: 1,
      onCreate: _createDB,
    );
  }
  
  Future<void> _createDB(Database db, int version) async {
    await db.execute('''
      CREATE TABLE users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        email TEXT NOT NULL
      )
    ''');
  }
  
  // 插入数据
  Future<int> insertUser(Map<String, dynamic> user) async {
    final db = await database;
    return await db.insert('users', user);
  }
  
  // 查询所有
  Future<List<Map<String, dynamic>>> getAllUsers() async {
    final db = await database;
    return await db.query('users');
  }
  
  // 查询单个
  Future<Map<String, dynamic>?> getUser(int id) async {
    final db = await database;
    final results = await db.query(
      'users',
      where: 'id = ?',
      whereArgs: [id],
    );
    
    if (results.isNotEmpty) {
      return results.first;
    }
    return null;
  }
  
  // 更新
  Future<int> updateUser(int id, Map<String, dynamic> user) async {
    final db = await database;
    return await db.update(
      'users',
      user,
      where: 'id = ?',
      whereArgs: [id],
    );
  }
  
  // 删除
  Future<int> deleteUser(int id) async {
    final db = await database;
    return await db.delete(
      'users',
      where: 'id = ?',
      whereArgs: [id],
    );
  }
  
  // 关闭数据库
  Future<void> close() async {
    final db = await database;
    await db.close();
  }
}

3. 文件存储

import 'dart:io';
import 'package:path_provider/path_provider.dart';

class FileService {
  // 获取应用文档目录
  static Future<File> getLocalFile(String filename) async {
    final directory = await getApplicationDocumentsDirectory();
    final path = directory.path;
    return File('$path/$filename');
  }
  
  // 写入文件
  static Future<File> writeFile(String filename, String content) async {
    final file = await getLocalFile(filename);
    return await file.writeAsString(content);
  }
  
  // 读取文件
  static Future<String> readFile(String filename) async {
    try {
      final file = await getLocalFile(filename);
      return await file.readAsString();
    } catch (e) {
      return '';
    }
  }
  
  // 删除文件
  static Future<void> deleteFile(String filename) async {
    try {
      final file = await getLocalFile(filename);
      await file.delete();
    } catch (e) {
      print('删除文件失败: $e');
    }
  }
}

状态管理与数据

1. 使用 Provider 管理数据状态

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

// 数据模型
class User {
  final int id;
  final String name;
  final String email;
  
  User({required this.id, required this.name, required this.email});
  
  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'],
      name: json['name'],
      email: json['email'],
    );
  }
}

// 状态管理类
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 http.get(
        Uri.parse('https://api.example.com/users'),
      );
      
      if (response.statusCode == 200) {
        final List<dynamic> jsonList = jsonDecode(response.body);
        _users = jsonList
            .map((json) => User.fromJson(json as Map<String, dynamic>))
            .toList();
      } else {
        _error = '获取数据失败';
      }
    } catch (e) {
      _error = '网络错误: $e';
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }
  
  // 添加用户
  Future<void> addUser(User user) async {
    try {
      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) {
        _users.add(user);
        notifyListeners();
      }
    } catch (e) {
      _error = '添加用户失败: $e';
      notifyListeners();
    }
  }
}

// 使用
class UserListScreen extends StatelessWidget {
  const UserListScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('用户列表')),
      body: Consumer<UserProvider>(
        builder: (context, userProvider, child) {
          if (userProvider.isLoading) {
            return const Center(child: CircularProgressIndicator());
          }
          
          if (userProvider.error != null) {
            return Center(child: Text('错误: ${userProvider.error}'));
          }
          
          return ListView.builder(
            itemCount: userProvider.users.length,
            itemBuilder: (context, index) {
              final user = userProvider.users[index];
              return ListTile(
                title: Text(user.name),
                subtitle: Text(user.email),
              );
            },
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          context.read<UserProvider>().fetchUsers();
        },
        child: const Icon(Icons.refresh),
      ),
    );
  }
}

2. 使用 FutureBuilder

class UserListScreen extends StatelessWidget {
  const UserListScreen({super.key});

  Future<List<User>> fetchUsers() async {
    final response = await http.get(
      Uri.parse('https://api.example.com/users'),
    );
    
    if (response.statusCode == 200) {
      final List<dynamic> jsonList = jsonDecode(response.body);
      return jsonList
          .map((json) => User.fromJson(json as Map<String, dynamic>))
          .toList();
    } else {
      throw Exception('获取数据失败');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('用户列表')),
      body: FutureBuilder<List<User>>(
        future: fetchUsers(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(child: CircularProgressIndicator());
          }
          
          if (snapshot.hasError) {
            return Center(child: Text('错误: ${snapshot.error}'));
          }
          
          if (!snapshot.hasData) {
            return const Center(child: Text('没有数据'));
          }
          
          final users = snapshot.data!;
          return ListView.builder(
            itemCount: users.length,
            itemBuilder: (context, index) {
              final user = users[index];
              return ListTile(
                title: Text(user.name),
                subtitle: Text(user.email),
              );
            },
          );
        },
      ),
    );
  }
}

错误处理

1. 网络错误处理

class ApiException implements Exception {
  final String message;
  final int? statusCode;
  
  ApiException(this.message, {this.statusCode});
  
  @override
  String toString() => message;
}

class ApiService {
  static Future<Map<String, dynamic>> get(String endpoint) async {
    try {
      final response = await http.get(
        Uri.parse('https://api.example.com$endpoint'),
      );
      
      if (response.statusCode == 200) {
        return jsonDecode(response.body) as Map<String, dynamic>;
      } else {
        throw ApiException(
          '请求失败',
          statusCode: response.statusCode,
        );
      }
    } on SocketException {
      throw ApiException('网络连接失败,请检查网络');
    } on TimeoutException {
      throw ApiException('请求超时,请重试');
    } on FormatException {
      throw ApiException('数据格式错误');
    } catch (e) {
      throw ApiException('未知错误: $e');
    }
  }
}

2. 重试机制

Future<T> retryRequest<T>(
  Future<T> Function() request, {
  int maxRetries = 3,
  Duration delay = const Duration(seconds: 1),
}) async {
  int attempts = 0;
  
  while (attempts < maxRetries) {
    try {
      return await request();
    } catch (e) {
      attempts++;
      if (attempts >= maxRetries) {
        rethrow;
      }
      await Future.delayed(delay);
    }
  }
  
  throw Exception('重试失败');
}

缓存策略

1. 简单的内存缓存

class CacheService {
  static final Map<String, CacheItem> _cache = {};
  
  static void set(String key, dynamic value, {Duration? expiry}) {
    _cache[key] = CacheItem(
      value: value,
      expiry: expiry != null ? DateTime.now().add(expiry) : null,
    );
  }
  
  static dynamic get(String key) {
    final item = _cache[key];
    if (item == null) return null;
    
    if (item.expiry != null && DateTime.now().isAfter(item.expiry!)) {
      _cache.remove(key);
      return null;
    }
    
    return item.value;
  }
  
  static void clear() {
    _cache.clear();
  }
}

class CacheItem {
  final dynamic value;
  final DateTime? expiry;
  
  CacheItem({required this.value, this.expiry});
}

2. 使用 cached_network_image

安装:

dependencies:
  cached_network_image: ^3.3.0

使用:

CachedNetworkImage(
  imageUrl: 'https://example.com/image.jpg',
  placeholder: (context, url) => const CircularProgressIndicator(),
  errorWidget: (context, url, error) => const Icon(Icons.error),
)

与后端 API 交互

1. RESTful API 示例

class UserApi {
  static const String baseUrl = 'https://api.example.com';
  
  // 获取所有用户
  static Future<List<User>> getUsers() async {
    final response = await http.get(Uri.parse('$baseUrl/users'));
    // ... 处理响应
  }
  
  // 获取单个用户
  static Future<User> getUser(int id) async {
    final response = await http.get(Uri.parse('$baseUrl/users/$id'));
    // ... 处理响应
  }
  
  // 创建用户
  static Future<User> createUser(User user) async {
    final response = await http.post(
      Uri.parse('$baseUrl/users'),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode(user.toJson()),
    );
    // ... 处理响应
  }
  
  // 更新用户
  static Future<User> updateUser(int id, User user) async {
    final response = await http.put(
      Uri.parse('$baseUrl/users/$id'),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode(user.toJson()),
    );
    // ... 处理响应
  }
  
  // 删除用户
  static Future<void> deleteUser(int id) async {
    final response = await http.delete(
      Uri.parse('$baseUrl/users/$id'),
    );
    // ... 处理响应
  }
}

2. WebSocket 通信

安装:

dependencies:
  web_socket_channel: ^2.4.0

使用:

import 'package:web_socket_channel/web_socket_channel.dart';

class WebSocketService {
  WebSocketChannel? _channel;
  
  void connect(String url) {
    _channel = WebSocketChannel.connect(Uri.parse(url));
  }
  
  void sendMessage(String message) {
    _channel?.sink.add(message);
  }
  
  Stream<dynamic> get stream => _channel?.stream ?? const Stream.empty();
  
  void disconnect() {
    _channel?.sink.close();
  }
}

最佳实践

1. 代码组织

lib/
  models/
    user.dart
    product.dart
  services/
    api_service.dart
    database_service.dart
    cache_service.dart
  providers/
    user_provider.dart
    product_provider.dart
  screens/
    user_list_screen.dart
    user_detail_screen.dart

2. 使用 Repository 模式

abstract class UserRepository {
  Future<List<User>> getUsers();
  Future<User> getUser(int id);
  Future<User> createUser(User user);
  Future<User> updateUser(int id, User user);
  Future<void> deleteUser(int id);
}

class UserRepositoryImpl implements UserRepository {
  final ApiService _apiService;
  final DatabaseHelper _databaseHelper;
  
  UserRepositoryImpl(this._apiService, this._databaseHelper);
  
  @override
  Future<List<User>> getUsers() async {
    try {
      // 先尝试从网络获取
      final users = await _apiService.getUsers();
      // 保存到本地数据库
      await _databaseHelper.saveUsers(users);
      return users;
    } catch (e) {
      // 网络失败,从本地获取
      return await _databaseHelper.getUsers();
    }
  }
  
  // ... 其他方法
}

3. 环境配置

class AppConfig {
  static const String apiBaseUrl = String.fromEnvironment(
    'API_BASE_URL',
    defaultValue: 'https://api.example.com',
  );
  
  static const bool enableLogging = bool.fromEnvironment(
    'ENABLE_LOGGING',
    defaultValue: false,
  );
}

常见问题

1. CORS 错误(Web)

在 Web 开发中,如果遇到 CORS 错误,需要在后端配置 CORS 头。

2. 证书错误(Android)

android/app/src/main/res/xml/network_security_config.xml 中配置:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true">
        <trust-anchors>
            <certificates src="system" />
        </trust-anchors>
    </base-config>
</network-security-config>

3. 网络权限(Android)

AndroidManifest.xml 中添加:

<uses-permission android:name="android.permission.INTERNET"/>

总结

Flutter 的数据调用和后端交互涉及多个方面:

  1. 网络请求:使用 httpdio
  2. JSON 序列化:手动或使用 json_serializable
  3. 数据持久化:SharedPreferences、SQLite、文件存储
  4. 状态管理:Provider、FutureBuilder 等
  5. 错误处理:完善的异常处理机制
  6. 缓存策略:提高性能和用户体验

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容