Flutter Realm 教程

Flutter Realm 教程:构建跨平台数据同步应用

🌟 前言

在现代移动应用开发中,数据同步和本地存储是不可或缺的功能。今天我来分享一个使用 Realm 数据库的完整示例,展示如何在 Flutter 应用中实现跨平台的本地数据存储和实时同步功能。Realm 是一个现代化的对象数据库,不仅提供了高性能的本地存储,还支持云端数据同步,让你的应用能够在多设备间无缝同步数据。

🚀 为什么选择 Realm?

Realm 相比其他数据库解决方案具有以下优势:

  • 🌐 跨平台一致性:支持 Android、iOS、Flutter 等多个平台
  • 🔄 实时同步:支持云端数据同步,多设备实时更新
  • 🛡️ 数据加密:内置加密功能保护敏感数据
  • 🎯 对象 API:直观的对象导向数据访问方式
  • 高性能:优化的查询引擎和内存管理
  • 📱 响应式编程:支持数据变化的实时监听

📦 项目配置

首先,让我们配置项目依赖。在 pubspec.yaml 中添加:

yaml 复制代码
name: realm_example
description: "A new Flutter project."
publish_to: 'none'
version: 0.1.0

environment:
  sdk: '>=3.3.4 <4.0.0'

dependencies:
  flutter:
    sdk: flutter
  realm: ^20.0.1

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^3.0.0

flutter:
  uses-material-design: true

🏗️ 数据模型设计

1. 定义 Person 和 Address 实体类

让我们从一个人员管理应用开始,定义包含关系映射的数据模型:

dart 复制代码
import 'package:realm/realm.dart';

/// 导入自动生成的 Realm 模型文件
part 'person.realm.dart';

/// 人员模型类
/// 使用 @RealmModel() 注解标记为 Realm 数据模型
@RealmModel()
class _Person {
  /// 主键ID,用于唯一标识每个人员记录
  @PrimaryKey()
  late String id;
  
  /// 人员姓名,必填字段
  late String name;
  
  /// 人员年龄,必填字段
  late int age;
  
  /// 电子邮箱,可选字段
  String? email;
  
  /// 关联的地址信息,可选字段(一对一关系)
  late _Address? addresses;
}

/// 地址模型类
@RealmModel()
class _Address {
  /// 主键ID,用于唯一标识每个地址记录
  @PrimaryKey()
  late String id;
  
  /// 街道地址,必填字段
  late String street;
  
  /// 城市名称,必填字段
  late String city;
  
  /// 国家名称,必填字段
  late String country;
  
  /// 邮政编码,可选字段
  String? postalCode;
}

2. 关键注解说明

  • @RealmModel():标记类为 Realm 数据模型
  • @PrimaryKey():定义主键,确保数据唯一性
  • part 'person.realm.dart':引入自动生成的 Realm 代码
  • late:延迟初始化关键字,适用于必填字段
  • 关系映射:通过对象引用实现一对一、一对多关系

💾 数据库管理器

创建一个单例模式的数据库管理器:

dart 复制代码
import 'package:realm/realm.dart';
import 'package:realm_example/models/person.dart';

/// 数据库管理器类
/// 负责管理 Realm 数据库的初始化、配置和生命周期
class DatabaseManager {
  /// 单例实例
  static DatabaseManager? _instance;
  
  /// Realm 数据库实例
  late final Realm _realm;

  /// 私有构造函数,用于实现单例模式
  DatabaseManager._() {
    // 配置 Realm 数据库
    final config = Configuration.local(
      /// 定义数据库中的模型架构
      [Person.schema, Address.schema],
      
      /// 设置数据库版本号,用于数据库升级
      schemaVersion: 1,
      
      /// 数据库版本迁移回调函数
      migrationCallback: (migration, oldSchemaVersion) {
        // 在这里实现数据库版本迁移逻辑
        // 例如:添加新字段、修改字段类型、删除字段等
        print('从版本 $oldSchemaVersion 升级到版本 1');
      },
    );
    
    /// 创建 Realm 数据库实例
    _realm = Realm(config);
  }

  /// 获取数据库管理器单例实例
  static Future<DatabaseManager> getInstance() async {
    /// 如果实例不存在,则创建新实例
    _instance ??= DatabaseManager._();
    return _instance!;
  }

  /// 获取 Realm 数据库实例
  Realm get realm => _realm;

  /// 关闭数据库连接
  void close() {
    _realm.close();
  }
}

🗂️ 仓库层实现

使用仓库模式封装数据访问逻辑:

dart 复制代码
import 'package:realm/realm.dart';
import 'package:realm_example/database/database_manager.dart';
import 'package:realm_example/models/person.dart';

/// 人员数据仓库类
/// 负责处理人员相关的数据库操作,包括增删查改
class PersonRepository {
  final DatabaseManager _databaseManager;
  late final Realm _realm;

  PersonRepository(this._databaseManager) {
    _realm = _databaseManager.realm;
  }

  /// 创建新的人员对象
  Person createPerson(String name, int age, {String? email}) {
    // 创建新的人员对象
    final person = Person(
      ObjectId().toString(), // 生成唯一ID
      name,
      age,
      email: email,
    );
    
    // 在数据库事务中保存人员对象
    _realm.write(() => _realm.add(person));
    return person;
  }

  /// 为人员添加地址信息
  void addAddress(Person person, String street, String city, String country,
      {String? postalCode}) {
    // 创建新的地址对象
    final address = Address(
      ObjectId().toString(),
      street,
      city,
      country,
      postalCode: postalCode,
    );
    
    // 在数据库事务中更新人员的地址信息
    _realm.write(() {
      person.addresses = address;
    });
  }

  /// 获取所有人员对象
  List<Person> getAllPersons() {
    return _realm.all<Person>().toList();
  }

  /// 根据ID查找人员
  Person? getPersonById(String id) {
    return _realm.find<Person>(id);
  }

  /// 根据姓名查找人员
  List<Person> findPersonsByName(String name) {
    return _realm.query<Person>(r'name == "$0"', [name]).toList();
  }

  /// 更新人员信息
  void updatePerson(Person person, {String? name, int? age, String? email}) {
    // 在数据库事务中更新人员信息
    _realm.write(() {
      if (name != null) person.name = name;
      if (age != null) person.age = age;
      if (email != null) person.email = email;
    });
  }

  /// 删除人员
  void deletePerson(Person person) {
    // 在数据库事务中删除人员
    _realm.write(() {
      _realm.delete(person);
    });
  }

  /// 删除人员的地址
  void deleteAddress(Person person, Address address) {
    // 在数据库事务中删除地址
    _realm.write(() {
      person.addresses = null; // 清空人员的地址关联
      _realm.delete(address);
    });
  }
}

🎨 UI 实现

创建一个完整的人员管理界面:

dart 复制代码
import 'package:flutter/material.dart';
import 'package:realm_example/database/database_manager.dart';
import 'package:realm_example/models/person.dart';
import 'package:realm_example/repositories/person_repository.dart';

/// 应用程序主入口
void main() async {
  // 确保 Flutter 绑定已初始化
  WidgetsFlutterBinding.ensureInitialized();
  
  // 获取数据库管理器实例
  final databaseManager = await DatabaseManager.getInstance();
  
  // 启动应用程序
  runApp(MyApp(databaseManager: databaseManager));
}

class MyApp extends StatelessWidget {
  final DatabaseManager databaseManager;

  const MyApp({super.key, required this.databaseManager});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Realm示例',
      home: PersonListPage(databaseManager: databaseManager),
    );
  }
}

class PersonListPage extends StatefulWidget {
  final DatabaseManager databaseManager;

  const PersonListPage({super.key, required this.databaseManager});

  @override
  State<PersonListPage> createState() => _PersonListPageState();
}

class _PersonListPageState extends State<PersonListPage> {
  late PersonRepository _personRepository;
  List<Person> _persons = [];

  @override
  void initState() {
    super.initState();
    _personRepository = PersonRepository(widget.databaseManager);
    _loadPersons();
  }

  /// 加载所有人员数据
  void _loadPersons() {
    setState(() {
      _persons = _personRepository.getAllPersons();
    });
  }

  /// 添加新人员
  Future<void> _addPerson() async {
    // 创建新人员
    final person = _personRepository.createPerson(
      'name', 
      25, 
      email: 'realm@example.com'
    );
    
    // 为人员添加地址
    _personRepository.addAddress(
      person,
      '某某路某某号',
      '上海',
      '中国',
      postalCode: '000000',
    );
    
    // 重新加载人员列表
    _loadPersons();
  }

  /// 更新人员信息
  void _updatePerson(Person person) {
    _personRepository.updatePerson(
      person,
      name: '${person.name}(更新)',
      age: person.age + 1,
    );
    _loadPersons();
  }

  /// 删除人员
  void _deletePerson(Person person) {
    _personRepository.deletePerson(person);
    _loadPersons();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Realm数据库示例'),
      ),
      body: ListView.builder(
        itemCount: _persons.length,
        itemBuilder: (context, index) {
          final person = _persons[index];
          return Card(
            margin: const EdgeInsets.all(8.0),
            child: ListTile(
              title: Text(person.name),
              subtitle: Text(
                '''
                年龄: ${person.age}
                邮箱: ${person.email ?? "无"}
                街道: ${person.addresses?.street ?? "无"}
                城市: ${person.addresses?.city ?? "无"}
                国家: ${person.addresses?.country ?? "无"}
                编码: ${person.addresses?.postalCode ?? "无"}
                ''',
              ),
              trailing: Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  IconButton(
                    icon: const Icon(Icons.edit),
                    onPressed: () => _updatePerson(person),
                  ),
                  IconButton(
                    icon: const Icon(Icons.delete),
                    onPressed: () => _deletePerson(person),
                  ),
                ],
              ),
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _addPerson,
        child: const Icon(Icons.add),
      ),
    );
  }
}

🔧 代码生成

在修改模型后,需要运行代码生成命令:

bash 复制代码
dart run realm generate

这会生成 person.realm.dart 文件,包含必要的 Realm 对象代码。

💡 核心特性详解

1. 版本管理

Realm 通过 schemaVersionmigrationCallback 实现版本管理:

dart 复制代码
final config = Configuration.local(
  [Person.schema, Address.schema],
  schemaVersion: 2, // 增加版本号
  migrationCallback: (migration, oldSchemaVersion) {
    // 处理从旧版本到新版本的数据迁移
    if (oldSchemaVersion < 2) {
      // 添加新字段的默认值
      migration.renameProperty('Person', 'oldName', 'name');
    }
  },
);

2. 查询功能

Realm 提供了强大的查询功能:

dart 复制代码
// 简单查询
var adults = realm.query<Person>('age >= 18');

// 条件查询
var emailUsers = realm.query<Person>('email != nil');

// 排序查询
var sortedPersons = realm.query<Person>('TRUEPREDICATE SORT(age ASC)');

// 复合查询
var query = realm.query<Person>('name CONTAINS "张" AND age > 20');

3. 关系映射

Realm 支持多种关系类型:

dart 复制代码
// 一对一关系
@RealmModel()
class _Person {
  late _Address? addresses; // 可选的一对一关系
}

// 一对多关系
@RealmModel()
class _Person {
  late List<_Address> addresses; // 一对多关系
}

// 反向关系
@RealmModel()
class _Address {
  @Backlink(#addresses)
  late Iterable<_Person> persons; // 反向关系
}

4. 响应式编程

Realm 支持数据变化的实时监听:

dart 复制代码
// 监听集合变化
realm.all<Person>().changes.listen((changes) {
  // 处理数据变化
  print('插入: ${changes.inserted}');
  print('删除: ${changes.deleted}');
  print('修改: ${changes.modified}');
});

// 监听对象变化
person.changes.listen((change) {
  print('对象发生变化: ${change.object}');
});

🌐 云端同步功能

1. 配置同步

dart 复制代码
final app = App(AppConfiguration('your-app-id'));
final user = await app.logIn(Credentials.anonymous());

final config = Configuration.flexibleSync(
  user,
  [Person.schema, Address.schema],
);

final realm = Realm(config);

// 添加同步订阅
realm.subscriptions.update((mutableSubscriptions) {
  mutableSubscriptions.add(realm.all<Person>());
});

2. 权限控制

dart 复制代码
// 基于用户的数据隔离
@RealmModel()
class _Person {
  @PrimaryKey()
  late String id;
  
  late String ownerId; // 用户ID,用于权限控制
  late String name;
  late int age;
}

🎯 最佳实践

1. 数据库初始化

dart 复制代码
class DatabaseService {
  static DatabaseService? _instance;
  late final Realm _realm;
  
  static Future<DatabaseService> getInstance() async {
    if (_instance == null) {
      _instance = DatabaseService._();
      await _instance!._init();
    }
    return _instance!;
  }
  
  DatabaseService._();
  
  Future<void> _init() async {
    final config = Configuration.local([Person.schema, Address.schema]);
    _realm = Realm(config);
  }
}

2. 错误处理

dart 复制代码
try {
  realm.write(() {
    realm.add(person);
  });
} on RealmException catch (e) {
  print('Realm 错误: ${e.message}');
} catch (e) {
  print('其他错误: $e');
}

3. 内存管理

dart 复制代码
// 正确关闭 Realm 实例
@override
void dispose() {
  realm.close();
  super.dispose();
}

// 使用 Realm 的冻结功能传递数据
final frozenPerson = person.freeze();

📊 性能优化技巧

1. 批量操作

dart 复制代码
// 批量插入
realm.write(() {
  for (var personData in personDataList) {
    realm.add(Person(personData));
  }
});

// 使用事务减少写入次数
realm.write(() {
  // 多个操作在同一事务中
  realm.add(person1);
  realm.add(person2);
  realm.delete(oldPerson);
});

2. 索引优化

dart 复制代码
@RealmModel()
class _Person {
  @PrimaryKey()
  late String id;
  
  @Indexed() // 添加索引提高查询性能
  late String name;
  
  late int age;
}

3. 查询优化

dart 复制代码
// 使用 limit 限制结果数量
var results = realm.query<Person>('age > 18 LIMIT(10)');

// 使用 distinct 去重
var uniqueNames = realm.query<Person>('DISTINCT(name)');

🚨 常见问题与解决方案

1. 模型变更处理

dart 复制代码
// 字段重命名
migrationCallback: (migration, oldSchemaVersion) {
  if (oldSchemaVersion < 2) {
    migration.renameProperty('Person', 'oldName', 'newName');
  }
}

// 字段类型变更
migrationCallback: (migration, oldSchemaVersion) {
  if (oldSchemaVersion < 2) {
    var oldObjects = migration.findInNewRealm('Person');
    for (var obj in oldObjects) {
      obj['age'] = int.parse(obj['age']); // String -> int
    }
  }
}

2. 同步冲突处理

dart 复制代码
// 自定义冲突解决策略
final config = Configuration.flexibleSync(
  user,
  [Person.schema],
  clientResetHandler: ManualRecoveryHandler(
    onClientReset: (realm, clientResetError) {
      // 处理客户端重置
    },
  ),
);

3. 性能监控

dart 复制代码
// 启用性能监控
final config = Configuration.local(
  [Person.schema],
  shouldCompactOnLaunch: (totalBytes, usedBytes) {
    // 当已使用空间小于总空间的50%时进行压缩
    return (usedBytes / totalBytes) < 0.5;
  },
);

🎉 总结

Realm 为 Flutter 应用提供了一个功能强大的数据库解决方案。通过本教程的学习,你应该能够:

  1. ✅ 理解 Realm 的核心概念和优势
  2. ✅ 掌握数据模型的定义和关系映射
  3. ✅ 实现完整的数据库管理架构
  4. ✅ 构建功能完整的 CRUD 操作
  5. ✅ 了解版本管理和数据迁移
  6. ✅ 实现响应式数据更新
  7. ✅ 配置云端数据同步

Realm 的对象导向 API、自动同步功能和强大的查询能力,使其成为现代 Flutter 应用的理想选择。在实际项目中,建议根据具体需求选择本地存储或云端同步模式,充分利用 Realm 的各项特性来构建高效、可扩展的数据层。

🔗 相关资源


希望这个教程对你有所帮助!如果你有任何问题或建议,欢迎在评论区交流讨论。

相关推荐
GoldKey2 小时前
gcc 源码阅读---语法树
linux·前端·windows
Xf3n1an3 小时前
html语法
前端·html
张拭心3 小时前
亚马逊 AI IDE Kiro “狙击”Cursor?实测心得
前端·ai编程
烛阴3 小时前
为什么你的Python项目总是混乱?层级包构建全解析
前端·python
余很多之很多4 小时前
flutter下的webview适配rem问题
flutter
@大迁世界4 小时前
React 及其生态新闻 — 2025年6月
前端·javascript·react.js·前端框架·ecmascript
红尘散仙5 小时前
Rust 终端 UI 开发新玩法:用 Ratatui Kit 轻松打造高颜值 CLI
前端·后端·rust
新酱爱学习5 小时前
前端海报生成的几种方式:从 Canvas 到 Skyline
前端·javascript·微信小程序
袁煦丞5 小时前
把纸堆变数据流!Paperless-ngx让文件管理像打游戏一样爽:cpolar内网穿透实验室第539个成功挑战
前端·程序员·远程工作
慧慧吖@5 小时前
关于两种网络攻击方式XSS和CSRF
前端·xss·csrf