?? 目錄
- 概述
- 網(wǎng)絡(luò)請求(HTTP)
- JSON 序列化和反序列化
- 數(shù)據(jù)持久化
- 狀態(tài)管理與數(shù)據(jù)
- 錯(cuò)誤處理
- 緩存策略
- 與后端 API 交互
- 最佳實(shí)踐
- 常見問題
概述
Flutter 應(yīng)用需要與后端服務(wù)交互來獲取和發(fā)送數(shù)據(jù)。主要涉及:
- 網(wǎng)絡(luò)請求:使用 HTTP 協(xié)議與服務(wù)器通信
- 數(shù)據(jù)序列化:將 JSON 數(shù)據(jù)轉(zhuǎn)換為 Dart 對象
- 數(shù)據(jù)持久化:本地存儲(chǔ)數(shù)據(jù)(SharedPreferences、SQLite、文件等)
- 狀態(tài)管理:管理異步數(shù)據(jù)的狀態(tài)
- 錯(cuò)誤處理:處理網(wǎng)絡(luò)錯(cuò)誤和異常
網(wǎng)絡(luò)請求(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('數(shù)據(jù)發(fā)送成功');
}
}
2. 完整的 HTTP 服務(wù)類
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('網(wǎng)絡(luò)請求失敗: $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('網(wǎng)絡(luò)請求失敗: $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('網(wǎng)絡(luò)請求失敗: $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('網(wǎng)絡(luò)請求失敗: $e');
}
}
// 處理響應(yīng)
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. 帶認(rèn)證的請求
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 是一個(gè)功能更強(qiáng)大的 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);
}
}
// 錯(cuò)誤處理
String _handleError(DioException error) {
switch (error.type) {
case DioExceptionType.connectionTimeout:
return '連接超時(shí)';
case DioExceptionType.sendTimeout:
return '發(fā)送超時(shí)';
case DioExceptionType.receiveTimeout:
return '接收超時(shí)';
case DioExceptionType.badResponse:
return '服務(wù)器錯(cuò)誤: ${error.response?.statusCode}';
case DioExceptionType.cancel:
return '請求已取消';
default:
return '網(wǎng)絡(luò)錯(cuò)誤: ${error.message}';
}
}
}
JSON 序列化和反序列化
1. 手動(dòng)序列化
class User {
final int id;
final String name;
final String email;
User({
required this.id,
required this.name,
required this.email,
});
// 從 JSON 創(chuàng)建對象
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'] as int,
name: json['name'] as String,
email: json['email'] as String,
);
}
// 轉(zhuǎn)換為 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);
// 轉(zhuǎn)換為 JSON
final userJson = user.toJson();
final jsonString2 = jsonEncode(userJson);
}
2. 使用 json_serializable(推薦)
自動(dòng)生成序列化代碼,減少樣板代碼。
安裝:
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 數(shù)組
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();
數(shù)據(jù)持久化
1. SharedPreferences - 鍵值對存儲(chǔ)
安裝:
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);
}
// 保存整數(shù)
static Future<bool> saveInt(String key, int value) async {
return await _prefs?.setInt(key, value) ?? false;
}
// 讀取整數(shù)
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();
// 保存數(shù)據(jù)
await PreferencesService.saveString('username', 'John');
await PreferencesService.saveInt('age', 25);
// 讀取數(shù)據(jù)
final username = PreferencesService.getString('username');
final age = PreferencesService.getInt('age');
}
2. SQLite - 關(guān)系型數(shù)據(jù)庫
安裝:
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
)
''');
}
// 插入數(shù)據(jù)
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');
}
// 查詢單個(gè)
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],
);
}
// 關(guān)閉數(shù)據(jù)庫
Future<void> close() async {
final db = await database;
await db.close();
}
}
3. 文件存儲(chǔ)
import 'dart:io';
import 'package:path_provider/path_provider.dart';
class FileService {
// 獲取應(yīng)用文檔目錄
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');
}
}
}
狀態(tài)管理與數(shù)據(jù)
1. 使用 Provider 管理數(shù)據(jù)狀態(tài)
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// 數(shù)據(jù)模型
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'],
);
}
}
// 狀態(tài)管理類
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 = '獲取數(shù)據(jù)失敗';
}
} catch (e) {
_error = '網(wǎng)絡(luò)錯(cuò)誤: $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('錯(cuò)誤: ${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('獲取數(shù)據(jù)失敗');
}
}
@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('錯(cuò)誤: ${snapshot.error}'));
}
if (!snapshot.hasData) {
return const Center(child: Text('沒有數(shù)據(jù)'));
}
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),
);
},
);
},
),
);
}
}
錯(cuò)誤處理
1. 網(wǎng)絡(luò)錯(cuò)誤處理
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('網(wǎng)絡(luò)連接失敗,請檢查網(wǎng)絡(luò)');
} on TimeoutException {
throw ApiException('請求超時(shí),請重試');
} on FormatException {
throw ApiException('數(shù)據(jù)格式錯(cuò)誤');
} catch (e) {
throw ApiException('未知錯(cuò)誤: $e');
}
}
}
2. 重試機(jī)制
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. 簡單的內(nèi)存緩存
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'));
// ... 處理響應(yīng)
}
// 獲取單個(gè)用戶
static Future<User> getUser(int id) async {
final response = await http.get(Uri.parse('$baseUrl/users/$id'));
// ... 處理響應(yīng)
}
// 創(chuàng)建用戶
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()),
);
// ... 處理響應(yīng)
}
// 更新用戶
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()),
);
// ... 處理響應(yīng)
}
// 刪除用戶
static Future<void> deleteUser(int id) async {
final response = await http.delete(
Uri.parse('$baseUrl/users/$id'),
);
// ... 處理響應(yīng)
}
}
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();
}
}
最佳實(shí)踐
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 {
// 先嘗試從網(wǎng)絡(luò)獲取
final users = await _apiService.getUsers();
// 保存到本地?cái)?shù)據(jù)庫
await _databaseHelper.saveUsers(users);
return users;
} catch (e) {
// 網(wǎng)絡(luò)失敗,從本地獲取
return await _databaseHelper.getUsers();
}
}
// ... 其他方法
}
3. 環(huán)境配置
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 錯(cuò)誤(Web)
在 Web 開發(fā)中,如果遇到 CORS 錯(cuò)誤,需要在后端配置 CORS 頭。
2. 證書錯(cuò)誤(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. 網(wǎng)絡(luò)權(quán)限(Android)
在 AndroidManifest.xml 中添加:
<uses-permission android:name="android.permission.INTERNET"/>
總結(jié)
Flutter 的數(shù)據(jù)調(diào)用和后端交互涉及多個(gè)方面:
-
網(wǎng)絡(luò)請求:使用
http或dio包 -
JSON 序列化:手動(dòng)或使用
json_serializable - 數(shù)據(jù)持久化:SharedPreferences、SQLite、文件存儲(chǔ)
- 狀態(tài)管理:Provider、FutureBuilder 等
- 錯(cuò)誤處理:完善的異常處理機(jī)制
- 緩存策略:提高性能和用戶體驗(yàn)