← 返回学习路线
🌐

网络请求与数据持久化

掌握 Flutter 中的网络通信、数据序列化与本地存储技术,构建完整的数据层架构

📡 第一章:HTTP 基础

1.1 http 包简介

Flutter 官方提供的 http 包是进行网络请求的基础工具,支持常见的 HTTP 方法。

pubspec.yaml
# 添加 http 依赖
dependencies:
  http: ^1.2.0

1.2 GET 请求

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

// GET 请求:获取用户列表
Future<List<dynamic>> fetchUsers() async {
  final response = await http.get(
    Uri.parse('https://api.example.com/users'),
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json',
    },
  );

  if (response.statusCode == 200) {
    return jsonDecode(response.body);
  } else {
    throw Exception('请求失败:${response.statusCode}');
  }
}

1.3 POST 请求

Dart
// POST 请求:创建新用户
Future<Map<String, dynamic>> createUser(String name, String email) async {
  final response = await http.post(
    Uri.parse('https://api.example.com/users'),
    headers: {'Content-Type': 'application/json'},
    body: jsonEncode({
      'name': name,
      'email': email,
    }),
  );

  if (response.statusCode == 201) {
    return jsonDecode(response.body);
  } else {
    throw Exception('创建失败:${response.statusCode}');
  }
}

1.4 PUT 和 DELETE 请求

Dart
// PUT 请求:更新用户信息
Future<void> updateUser(int id, String name) async {
  final response = await http.put(
    Uri.parse('https://api.example.com/users/$id'),
    headers: {'Content-Type': 'application/json'},
    body: jsonEncode({'name': name}),
  );
  if (response.statusCode != 200) {
    throw Exception('更新失败');
  }
}

// DELETE 请求:删除用户
Future<void> deleteUser(int id, String token) async {
  final response = await http.delete(
    Uri.parse('https://api.example.com/users/$id'),
    headers: {'Authorization': 'Bearer $token'},
  );
  if (response.statusCode != 204) {
    throw Exception('删除失败');
  }
}

1.5 响应状态码与错误处理

网络请求中常见的状态码:200 成功、201 创建成功、400 请求错误、401 未授权、404 未找到、500 服务器错误。

Dart
import 'dart:io';
import 'dart:async';

Future<dynamic> safeRequest() async {
  try {
    final response = await http.get(
      Uri.parse('https://api.example.com/data'),
    ).timeout(const Duration(seconds: 10));

    switch (response.statusCode) {
      case 200:
        return jsonDecode(response.body);
      case 401:
        throw Exception('未授权,请重新登录');
      case 404:
        throw Exception('资源不存在');
      case 500:
        throw Exception('服务器内部错误');
      default:
        throw Exception('请求失败:${response.statusCode}');
    }
  } on SocketException {
    throw Exception('网络连接失败,请检查网络');
  } on TimeoutException {
    throw Exception('请求超时,请稍后重试');
  } on FormatException {
    throw Exception('数据格式解析错误');
  }
}
提示:http 包适合简单的网络请求场景。对于需要拦截器、取消请求、文件上传等高级功能,推荐使用 Dio。

🚀 第二章:Dio 进阶

2.1 安装与基本使用

pubspec.yaml
dependencies:
  dio: ^5.4.0
Dart
import 'package:dio/dio.dart';

// 基本 GET 请求
final dio = Dio();
final response = await dio.get('https://api.example.com/users');
print(response.data); // Dio 自动解析 JSON

2.2 Dio 实例配置

Dart
final dio = Dio(BaseOptions(
  baseUrl: 'https://api.example.com',
  connectTimeout: const Duration(seconds: 10),
  receiveTimeout: const Duration(seconds: 15),
  headers: {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
  },
));

2.3 拦截器

Dio 的拦截器系统允许你在请求发送前、响应返回后、以及错误发生时进行统一处理。

Dart
class AuthInterceptor extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    // 在每个请求中自动添加 token
    final token = AuthService.instance.token;
    if (token != null) {
      options.headers['Authorization'] = 'Bearer $token';
    }
    handler.next(options);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    // 统一处理响应日志
    print('[${response.statusCode}] ${response.requestOptions.path}');
    handler.next(response);
  }

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) {
    // 统一错误处理
    if (err.response?.statusCode == 401) {
      AuthService.instance.logout();
    }
    handler.next(err);
  }
}

// 注册拦截器
dio.interceptors.add(AuthInterceptor());

2.4 Token 刷新拦截器模式

Dart
class TokenRefreshInterceptor extends Interceptor {
  final Dio _dio;
  bool _isRefreshing = false;

  TokenRefreshInterceptor(this._dio);

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) async {
    if (err.response?.statusCode == 401 && !_isRefreshing) {
      _isRefreshing = true;
      try {
        // 使用 refreshToken 获取新的 accessToken
        final newToken = await _refreshToken();
        AuthService.instance.saveToken(newToken);

        // 用新 token 重试原始请求
        final opts = err.requestOptions;
        opts.headers['Authorization'] = 'Bearer $newToken';
        final response = await _dio.fetch(opts);
        handler.resolve(response);
      } catch (e) {
        handler.reject(err);
      } finally {
        _isRefreshing = false;
      }
    } else {
      handler.next(err);
    }
  }

  Future<String> _refreshToken() async {
    final resp = await Dio().post(
      'https://api.example.com/auth/refresh',
      data: {'refreshToken': AuthService.instance.refreshToken},
    );
    return resp.data['accessToken'];
  }
}

2.5 文件上传与下载

Dart
// 文件上传(带进度回调)
Future<void> uploadFile(String filePath) async {
  final formData = FormData.fromMap({
    'file': await MultipartFile.fromFile(
      filePath,
      filename: 'upload.jpg',
    ),
    'description': '用户头像',
  });

  await dio.post(
    '/upload',
    data: formData,
    onSendProgress: (sent, total) {
      final progress = (sent / total * 100).toStringAsFixed(1);
      print('上传进度:$progress%');
    },
  );
}

// 文件下载(带进度回调)
Future<void> downloadFile(String url, String savePath) async {
  await dio.download(
    url,
    savePath,
    onReceiveProgress: (received, total) {
      if (total != -1) {
        final progress = (received / total * 100).toStringAsFixed(1);
        print('下载进度:$progress%');
      }
    },
  );
}

2.6 请求取消

Dart
// 使用 CancelToken 取消请求
final cancelToken = CancelToken();

dio.get('/search',
  queryParameters: {'q': 'flutter'},
  cancelToken: cancelToken,
);

// 在用户切换页面或输入新关键词时取消
cancelToken.cancel('用户取消了搜索');

2.7 重试逻辑

Dart
Future<Response> requestWithRetry(
  String path, {
  int maxRetries = 3,
}) async {
  int retries = 0;
  while (true) {
    try {
      return await dio.get(path);
    } on DioException catch (e) {
      retries++;
      if (retries >= maxRetries) rethrow;
      // 指数退避策略
      await Future.delayed(Duration(seconds: retries * 2));
    }
  }
}

2.8 完整 ApiClient 类

Dart
class ApiClient {
  static final ApiClient _instance = ApiClient._internal();
  factory ApiClient() => _instance;

  late final Dio _dio;

  ApiClient._internal() {
    _dio = Dio(BaseOptions(
      baseUrl: 'https://api.example.com/v1',
      connectTimeout: const Duration(seconds: 10),
      receiveTimeout: const Duration(seconds: 15),
    ));
    _dio.interceptors.addAll([
      AuthInterceptor(),
      LogInterceptor(requestBody: true, responseBody: true),
    ]);
  }

  Future<T> get<T>(String path, {Map<String, dynamic>? params}) async {
    final r = await _dio.get(path, queryParameters: params);
    return r.data as T;
  }

  Future<T> post<T>(String path, {dynamic data}) async {
    final r = await _dio.post(path, data: data);
    return r.data as T;
  }

  Future<T> put<T>(String path, {dynamic data}) async {
    final r = await _dio.put(path, data: data);
    return r.data as T;
  }

  Future<T> delete<T>(String path) async {
    final r = await _dio.delete(path);
    return r.data as T;
  }
}

📋 第三章:JSON 序列化

3.1 手动 JSON 解析

Dart
import 'dart:convert';

// 手动解码 JSON 字符串
final jsonStr = '{"name": "张三", "age": 25}';
final Map<String, dynamic> map = jsonDecode(jsonStr);
print(map['name']); // 张三

// 手动编码为 JSON 字符串
final encoded = jsonEncode({'name': '李四', 'age': 30});
print(encoded); // {"name":"李四","age":30}

3.2 模型类与 fromJson / toJson

Dart
class User {
  final int id;
  final String name;
  final String email;
  final DateTime createdAt;

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

  // 从 JSON Map 构建对象
  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'] as int,
      name: json['name'] as String,
      email: json['email'] as String,
      createdAt: DateTime.parse(json['created_at']),
    );
  }

  // 将对象转为 JSON Map
  Map<String, dynamic> toJson() => {
    'id': id,
    'name': name,
    'email': email,
    'created_at': createdAt.toIso8601String(),
  };
}

3.3 json_serializable 代码生成

pubspec.yaml
dependencies:
  json_annotation: ^4.8.1

dev_dependencies:
  build_runner: ^2.4.0
  json_serializable: ^6.7.1
Dart
import 'package:json_annotation/json_annotation.dart';

part 'user.g.dart';

@JsonSerializable()
class User {
  final int id;
  final String name;
  final String email;

  @JsonKey(name: 'created_at')
  final DateTime createdAt;

  @JsonKey(defaultValue: false)
  final bool isActive;

  User({
    required this.id,
    required this.name,
    required this.email,
    required this.createdAt,
    this.isActive = false,
  });

  // 由代码生成器自动生成
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}

// 运行代码生成命令:
// dart run build_runner build

3.4 freezed 不可变模型 + JSON

Dart
import 'package:freezed_annotation/freezed_annotation.dart';

part 'user.freezed.dart';
part 'user.g.dart';

@freezed
class User with _$User {
  const factory User({
    required int id,
    required String name,
    required String email,
    @JsonKey(name: 'created_at') required DateTime createdAt,
    @Default(false) bool isActive,
  }) = _User;

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

3.5 嵌套对象与列表序列化

Dart
@JsonSerializable(explicitToJson: true)
class Post {
  final int id;
  final String title;
  final User author;         // 嵌套对象
  final List<String> tags;   // 字符串列表
  final List<Comment> comments; // 对象列表

  Post({
    required this.id,
    required this.title,
    required this.author,
    required this.tags,
    required this.comments,
  });

  factory Post.fromJson(Map<String, dynamic> json) => _$PostFromJson(json);
  Map<String, dynamic> toJson() => _$PostToJson(this);
}

// 解析列表
List<User> parseUsers(String jsonStr) {
  final List list = jsonDecode(jsonStr);
  return list.map((json) => User.fromJson(json)).toList();
}
注意:使用 explicitToJson: true 确保嵌套对象也会调用其 toJson 方法,否则嵌套对象只会输出 Instance of 'User'

🏗 第四章:Repository 模式

4.1 什么是 Repository 模式

Repository 模式将数据获取逻辑从业务逻辑中分离出来,提供统一的数据访问接口。它隐藏了数据来源的具体实现(网络、本地缓存、数据库),使代码更易测试和维护。

4.2 抽象接口定义

Dart
// 定义仓库抽象接口
abstract class UserRepository {
  Future<List<User>> getUsers();
  Future<User> getUserById(int id);
  Future<void> createUser(User user);
  Future<void> updateUser(User user);
  Future<void> deleteUser(int id);
}

4.3 远程数据源

Dart
class UserRemoteDataSource {
  final ApiClient _api;
  UserRemoteDataSource(this._api);

  Future<List<User>> fetchUsers() async {
    final data = await _api.get<List>('/users');
    return data.map((j) => User.fromJson(j)).toList();
  }

  Future<User> fetchUserById(int id) async {
    final data = await _api.get<Map<String, dynamic>>('/users/$id');
    return User.fromJson(data);
  }
}

4.4 本地数据源(缓存)

Dart
class UserLocalDataSource {
  final Box<User> _box;
  UserLocalDataSource(this._box);

  List<User> getCachedUsers() => _box.values.toList();

  Future<void> cacheUsers(List<User> users) async {
    await _box.clear();
    for (final user in users) {
      await _box.put(user.id, user);
    }
  }
}

4.5 缓存策略

常见的缓存策略有两种:

  • 缓存优先(Cache First):先返回本地缓存数据,同时在后台刷新
  • 网络优先(Network First):先尝试网络请求,失败时回退到缓存

4.6 使用 Result 模式处理错误

Dart
// 简单的 Result 类型
sealed class Result<T> {}

class Success<T> extends Result<T> {
  final T data;
  Success(this.data);
}

class Failure<T> extends Result<T> {
  final String message;
  Failure(this.message);
}

4.7 完整 Repository 实现

Dart
class UserRepositoryImpl implements UserRepository {
  final UserRemoteDataSource _remote;
  final UserLocalDataSource _local;

  UserRepositoryImpl(this._remote, this._local);

  // 网络优先策略
  @override
  Future<List<User>> getUsers() async {
    try {
      // 尝试从网络获取
      final users = await _remote.fetchUsers();
      // 成功后缓存到本地
      await _local.cacheUsers(users);
      return users;
    } catch (e) {
      // 网络失败,返回本地缓存
      final cached = _local.getCachedUsers();
      if (cached.isNotEmpty) return cached;
      rethrow;
    }
  }

  @override
  Future<User> getUserById(int id) async {
    return await _remote.fetchUserById(id);
  }

  @override
  Future<void> createUser(User user) async {
    await _remote._api.post('/users', data: user.toJson());
  }

  @override
  Future<void> updateUser(User user) async {
    await _remote._api.put('/users/${user.id}', data: user.toJson());
  }

  @override
  Future<void> deleteUser(int id) async {
    await _remote._api.delete('/users/$id');
  }
}

💾 第五章:SharedPreferences

5.1 安装与基本使用

pubspec.yaml
dependencies:
  shared_preferences: ^2.2.0
Dart
import 'package:shared_preferences/shared_preferences.dart';

// 存储数据
Future<void> saveSettings() async {
  final prefs = await SharedPreferences.getInstance();

  await prefs.setString('username', '张三');
  await prefs.setInt('age', 25);
  await prefs.setDouble('score', 98.5);
  await prefs.setBool('darkMode', true);
  await prefs.setStringList('tags', ['flutter', 'dart']);
}

// 读取数据
Future<void> loadSettings() async {
  final prefs = await SharedPreferences.getInstance();

  final username = prefs.getString('username') ?? '未设置';
  final age = prefs.getInt('age') ?? 0;
  final score = prefs.getDouble('score') ?? 0.0;
  final darkMode = prefs.getBool('darkMode') ?? false;
  final tags = prefs.getStringList('tags') ?? [];
}

// 删除与清空
Future<void> clearData() async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.remove('username'); // 删除单个
  await prefs.clear();            // 清空所有
}

5.2 封装类模式

将 SharedPreferences 封装为工具类,便于全局统一使用。

Dart
class PrefsManager {
  static late SharedPreferences _prefs;

  // 在 main() 中初始化
  static Future<void> init() async {
    _prefs = await SharedPreferences.getInstance();
  }

  // 主题模式
  static bool get isDarkMode => _prefs.getBool('darkMode') ?? false;
  static set isDarkMode(bool v) => _prefs.setBool('darkMode', v);

  // 语言设置
  static String get locale => _prefs.getString('locale') ?? 'zh';
  static set locale(String v) => _prefs.setString('locale', v);

  // 是否首次启动
  static bool get isFirstLaunch => _prefs.getBool('firstLaunch') ?? true;
  static set isFirstLaunch(bool v) => _prefs.setBool('firstLaunch', v);
}
注意:SharedPreferences 不适合存储敏感信息(如密码、token),请使用 flutter_secure_storage。也不适合存储大量结构化数据,请使用 SQLite 或 Hive。

🗄 第六章:SQLite 数据库

6.1 sqflite 安装

pubspec.yaml
dependencies:
  sqflite: ^2.3.0
  path: ^1.8.3

6.2 数据库创建与版本管理

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

class DatabaseHelper {
  static Database? _db;

  Future<Database> get database async {
    _db ??= await _initDB();
    return _db!;
  }

  Future<Database> _initDB() async {
    final dbPath = await getDatabasesPath();
    final path = join(dbPath, 'notes.db');

    return await openDatabase(
      path,
      version: 2,
      onCreate: (db, version) async {
        // 首次创建表
        await db.execute('''
          CREATE TABLE notes (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            title TEXT NOT NULL,
            content TEXT NOT NULL,
            created_at TEXT NOT NULL
          )
        ''');
      },
      onUpgrade: (db, oldVersion, newVersion) async {
        // 数据库迁移
        if (oldVersion < 2) {
          await db.execute(
            'ALTER TABLE notes ADD COLUMN is_pinned INTEGER DEFAULT 0',
          );
        }
      },
    );
  }
}

6.3 CRUD 操作

Dart
class NoteDao {
  final DatabaseHelper _dbHelper;
  NoteDao(this._dbHelper);

  // 创建笔记
  Future<int> insert(Note note) async {
    final db = await _dbHelper.database;
    return await db.insert('notes', note.toMap());
  }

  // 查询所有笔记
  Future<List<Note>> getAll() async {
    final db = await _dbHelper.database;
    final maps = await db.query('notes',
      orderBy: 'created_at DESC',
    );
    return maps.map((m) => Note.fromMap(m)).toList();
  }

  // 条件查询
  Future<List<Note>> search(String keyword) async {
    final db = await _dbHelper.database;
    final maps = await db.query(
      'notes',
      where: 'title LIKE ? OR content LIKE ?',
      whereArgs: ['%$keyword%', '%$keyword%'],
      orderBy: 'created_at DESC',
      limit: 20,
    );
    return maps.map((m) => Note.fromMap(m)).toList();
  }

  // 更新笔记
  Future<int> update(Note note) async {
    final db = await _dbHelper.database;
    return await db.update(
      'notes',
      note.toMap(),
      where: 'id = ?',
      whereArgs: [note.id],
    );
  }

  // 删除笔记
  Future<int> delete(int id) async {
    final db = await _dbHelper.database;
    return await db.delete('notes', where: 'id = ?', whereArgs: [id]);
  }
}

6.4 事务处理

Dart
// 在事务中批量操作,保证原子性
Future<void> batchInsert(List<Note> notes) async {
  final db = await _dbHelper.database;
  await db.transaction((txn) async {
    for (final note in notes) {
      await txn.insert('notes', note.toMap());
    }
  });
}

6.5 drift(类型安全的 SQL)

drift(原名 moor)是一个类型安全的 SQLite 封装库,提供编译时检查和代码生成。

Dart
import 'package:drift/drift.dart';

// 定义表结构
class Notes extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get title => text().withLength(min: 1, max: 100)();
  TextColumn get content => text()();
  DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
}

// 编译时类型安全的查询
Future<List<Note>> searchNotes(String query) {
  return (select(notes)
    ..where((n) => n.title.contains(query))
    ..orderBy([(n) => OrderingTerm.desc(n.createdAt)])
    ..limit(20))
  .get();
}

6.6 完整示例:笔记数据库模型

Dart
class Note {
  final int? id;
  final String title;
  final String content;
  final DateTime createdAt;

  Note({this.id, required this.title, required this.content, DateTime? createdAt})
    : createdAt = createdAt ?? DateTime.now();

  Map<String, dynamic> toMap() => {
    'id': id,
    'title': title,
    'content': content,
    'created_at': createdAt.toIso8601String(),
  };

  factory Note.fromMap(Map<String, dynamic> map) => Note(
    id: map['id'],
    title: map['title'],
    content: map['content'],
    createdAt: DateTime.parse(map['created_at']),
  );
}

📦 第七章:Hive & Isar

7.1 Hive 简介与设置

pubspec.yaml
dependencies:
  hive: ^2.2.3
  hive_flutter: ^1.1.0

dev_dependencies:
  hive_generator: ^2.0.1
  build_runner: ^2.4.0
Dart
import 'package:hive_flutter/hive_flutter.dart';

// 初始化 Hive
void main() async {
  await Hive.initFlutter();

  // 注册适配器
  Hive.registerAdapter(ContactAdapter());

  // 打开 Box
  await Hive.openBox<Contact>('contacts');

  runApp(MyApp());
}

7.2 Hive 数据模型

Dart
import 'package:hive/hive.dart';

part 'contact.g.dart';

@HiveType(typeId: 0)
class Contact extends HiveObject {
  @HiveField(0)
  String name;

  @HiveField(1)
  String phone;

  @HiveField(2)
  String? email;

  Contact({required this.name, required this.phone, this.email});
}

// 运行:dart run build_runner build

7.3 Hive CRUD 操作

Dart
final box = Hive.box<Contact>('contacts');

// 添加
final contact = Contact(name: '张三', phone: '13800138000');
await box.add(contact);           // 自动生成 key
await box.put('zhangsan', contact); // 自定义 key

// 读取
final c = box.get('zhangsan');
final all = box.values.toList();

// 更新
contact.phone = '13900139000';
await contact.save(); // HiveObject 提供的快捷方法

// 删除
await box.delete('zhangsan');
await contact.delete(); // 或直接删除对象

7.4 Isar:现代 NoSQL 方案

Dart
import 'package:isar/isar.dart';

part 'task.g.dart';

@collection
class Task {
  Id id = Isar.autoIncrement;

  late String title;
  late bool isCompleted;

  @Index()
  late DateTime createdAt;
}

// 查询示例
final isar = await Isar.open([TaskSchema]);

// 查询未完成的任务,按创建时间倒序
final tasks = await isar.tasks
  .filter()
  .isCompletedEqualTo(false)
  .sortByCreatedAtDesc()
  .findAll();

7.5 方案对比

特性sqfliteHiveIsar
类型关系型 (SQL)键值对 NoSQL文档型 NoSQL
查询能力完整 SQL基本过滤强大的查询构建器
性能中等非常快
类型安全需适配器编译时检查
Web 支持
适用场景复杂查询、关系数据设置、缓存通用本地存储

🔒 第八章:安全存储

8.1 flutter_secure_storage

敏感数据(密码、token、密钥)应使用安全存储。flutter_secure_storage 在 iOS 上使用 Keychain,在 Android 上使用 KeyStore 加密。

pubspec.yaml
dependencies:
  flutter_secure_storage: ^9.0.0
Dart
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class SecureStorageService {
  static const _storage = FlutterSecureStorage();

  // 存储 Token
  static Future<void> saveToken(String token) async {
    await _storage.write(key: 'auth_token', value: token);
  }

  // 读取 Token
  static Future<String?> getToken() async {
    return await _storage.read(key: 'auth_token');
  }

  // 存储用户密码
  static Future<void> savePassword(String password) async {
    await _storage.write(key: 'user_password', value: password);
  }

  // 删除指定键
  static Future<void> deleteToken() async {
    await _storage.delete(key: 'auth_token');
  }

  // 清除所有安全存储
  static Future<void> clearAll() async {
    await _storage.deleteAll();
  }

  // 检查是否存在
  static Future<bool> hasToken() async {
    final token = await _storage.read(key: 'auth_token');
    return token != null;
  }
}
平台说明:iOS 使用 Keychain Services(硬件加密),Android 使用 EncryptedSharedPreferences 或 KeyStore。数据在应用卸载后会被清除。

🔥 第九章:Firebase 集成

9.1 FlutterFire 初始化

pubspec.yaml
dependencies:
  firebase_core: ^2.24.0
  firebase_auth: ^4.16.0
  cloud_firestore: ^4.14.0
  firebase_storage: ^11.6.0
  firebase_messaging: ^14.7.0
Dart
import 'package:firebase_core/firebase_core.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

9.2 Firebase Auth:用户认证

Dart
import 'package:firebase_auth/firebase_auth.dart';

class AuthService {
  final _auth = FirebaseAuth.instance;

  // 监听登录状态
  Stream<User?> get authStateChanges => _auth.authStateChanges();

  // 邮箱注册
  Future<UserCredential> signUp(String email, String password) async {
    try {
      return await _auth.createUserWithEmailAndPassword(
        email: email,
        password: password,
      );
    } on FirebaseAuthException catch (e) {
      throw Exception('注册失败:${e.message}');
    }
  }

  // 邮箱登录
  Future<UserCredential> signIn(String email, String password) async {
    return await _auth.signInWithEmailAndPassword(
      email: email,
      password: password,
    );
  }

  // Google 登录
  Future<UserCredential> signInWithGoogle() async {
    final googleUser = await GoogleSignIn().signIn();
    final googleAuth = await googleUser!.authentication;
    final credential = GoogleAuthProvider.credential(
      accessToken: googleAuth.accessToken,
      idToken: googleAuth.idToken,
    );
    return await _auth.signInWithCredential(credential);
  }

  // 退出登录
  Future<void> signOut() => _auth.signOut();
}

9.3 Cloud Firestore

Dart
import 'package:cloud_firestore/cloud_firestore.dart';

class FirestoreService {
  final _db = FirebaseFirestore.instance;

  // 创建文档
  Future<void> addPost(Map<String, dynamic> data) async {
    await _db.collection('posts').add({
      ...data,
      'createdAt': FieldValue.serverTimestamp(),
    });
  }

  // 获取文档列表
  Future<List<Map<String, dynamic>>> getPosts() async {
    final snapshot = await _db
      .collection('posts')
      .orderBy('createdAt', descending: true)
      .limit(20)
      .get();
    return snapshot.docs.map((d) => {'id': d.id, ...d.data()}).toList();
  }

  // 实时监听
  Stream<QuerySnapshot> postsStream() {
    return _db
      .collection('posts')
      .orderBy('createdAt', descending: true)
      .snapshots();
  }

  // 更新文档
  Future<void> updatePost(String id, Map<String, dynamic> data) async {
    await _db.collection('posts').doc(id).update(data);
  }

  // 删除文档
  Future<void> deletePost(String id) async {
    await _db.collection('posts').doc(id).delete();
  }

  // 条件查询
  Future<List<Map<String, dynamic>>> getPostsByAuthor(String uid) async {
    final snapshot = await _db
      .collection('posts')
      .where('authorId', isEqualTo: uid)
      .get();
    return snapshot.docs.map((d) => d.data()).toList();
  }
}

9.4 Firebase Storage

Dart
import 'package:firebase_storage/firebase_storage.dart';

// 上传文件
Future<String> uploadImage(File file) async {
  final ref = FirebaseStorage.instance
    .ref()
    .child('images/${DateTime.now().millisecondsSinceEpoch}.jpg');

  await ref.putFile(file);
  return await ref.getDownloadURL();
}

9.5 Firebase Messaging

Dart
import 'package:firebase_messaging/firebase_messaging.dart';

// 初始化推送通知
Future<void> initFCM() async {
  final messaging = FirebaseMessaging.instance;

  // 请求权限(iOS)
  await messaging.requestPermission();

  // 获取 FCM token
  final token = await messaging.getToken();
  print('FCM Token: $token');

  // 前台消息处理
  FirebaseMessaging.onMessage.listen((message) {
    print('收到消息:${message.notification?.title}');
  });

  // 后台点击消息处理
  FirebaseMessaging.onMessageOpenedApp.listen((message) {
    print('用户点击了通知:${message.data}');
  });
}

🔌 第十章:WebSocket

10.1 Dart 原生 WebSocket

Dart
import 'dart:io';

Future<void> connectWebSocket() async {
  final ws = await WebSocket.connect('wss://echo.websocket.org');

  // 发送消息
  ws.add('Hello WebSocket!');

  // 监听消息
  ws.listen(
    (data) => print('收到:$data'),
    onDone: () => print('连接关闭'),
    onError: (err) => print('错误:$err'),
  );
}

10.2 web_socket_channel 包

pubspec.yaml
dependencies:
  web_socket_channel: ^2.4.0

10.3 完整示例:简单聊天

Dart
import 'package:web_socket_channel/web_socket_channel.dart';
import 'dart:async';

class ChatService {
  WebSocketChannel? _channel;
  Timer? _reconnectTimer;
  final _controller = StreamController<String>.broadcast();
  bool _isConnected = false;

  // 消息流,供 UI 监听
  Stream<String> get messages => _controller.stream;

  // 连接 WebSocket
  Future<void> connect(String url) async {
    try {
      _channel = WebSocketChannel.connect(Uri.parse(url));
      _isConnected = true;

      _channel!.stream.listen(
        (data) {
          _controller.add(data as String);
        },
        onDone: () {
          _isConnected = false;
          _scheduleReconnect(url);
        },
        onError: (err) {
          _isConnected = false;
          _scheduleReconnect(url);
        },
      );
    } catch (e) {
      _scheduleReconnect(url);
    }
  }

  // 发送消息
  void send(String message) {
    if (_isConnected && _channel != null) {
      _channel!.sink.add(message);
    }
  }

  // 自动重连策略
  void _scheduleReconnect(String url) {
    _reconnectTimer?.cancel();
    _reconnectTimer = Timer(
      const Duration(seconds: 5),
      () => connect(url),
    );
  }

  // 断开连接
  Future<void> disconnect() async {
    _reconnectTimer?.cancel();
    await _channel?.sink.close();
    _isConnected = false;
  }

  void dispose() {
    disconnect();
    _controller.close();
  }
}

// 在 Widget 中使用
class ChatScreen extends StatefulWidget {
  @override
  State<ChatScreen> createState() => _ChatScreenState();
}

class _ChatScreenState extends State<ChatScreen> {
  final _chat = ChatService();
  final _textController = TextEditingController();
  final List<String> _messages = [];

  @override
  void initState() {
    super.initState();
    _chat.connect('wss://chat.example.com/ws');
    _chat.messages.listen((msg) {
      setState(() => _messages.add(msg));
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Expanded(
          child: ListView.builder(
            itemCount: _messages.length,
            itemBuilder: (_, i) => ListTile(title: Text(_messages[i])),
          ),
        ),
        Row(
          children: [
            Expanded(child: TextField(controller: _textController)),
            IconButton(
              icon: Icon(Icons.send),
              onPressed: () {
                _chat.send(_textController.text);
                _textController.clear();
              },
            ),
          ],
        ),
      ],
    );
  }

  @override
  void dispose() {
    _chat.dispose();
    _textController.dispose();
    super.dispose();
  }
}

🎯 第十一章:实践练习

练习 1:天气应用

使用 Dio 调用公开天气 API,实现以下功能:

  1. 输入城市名搜索天气信息
  2. 使用 json_serializable 解析天气数据模型
  3. 用 SharedPreferences 保存最近搜索的城市
  4. 实现网络错误时的友好提示
  5. 添加下拉刷新功能

练习 2:笔记本应用

构建一个完整的笔记本应用,练习本地存储技术:

  1. 使用 SQLite(sqflite)存储笔记,支持增删改查
  2. 实现搜索功能(按标题或内容模糊查询)
  3. 支持笔记置顶和排序
  4. 使用 Repository 模式组织数据层代码
  5. 添加数据库版本迁移逻辑

练习 3:实时聊天

结合 Firebase 或 WebSocket 实现简易聊天功能:

  1. 使用 Firebase Auth 实现邮箱注册和登录
  2. 使用 Cloud Firestore 存储聊天消息并实时同步
  3. 使用 flutter_secure_storage 安全存储登录凭证
  4. 实现消息列表的实时更新(StreamBuilder)
  5. 添加推送通知(Firebase Messaging)