Flutter嵌入式数据库插件couchbase_lite_dart的使用

引言

Couchbase Lite 是一个功能强大的嵌入式 NoSQL 数据库,可在移动设备上本地运行。这是一个基于 Couchbase Lite C 库(CBL_C)的 Dart 端口,使用了 dart:ffi。

警告: 该项目仍处于早期开发阶段,API 尚未稳定,可能会有破坏性更改!
欢迎参与测试、文档编写和开发工作。您可以查看如何贡献


功能清单

以下是该插件的主要功能列表:

数据库

  • 基本操作
    • 打开、关闭、复制、压缩、删除数据库。
    • 批量操作(类似于事务)。
    • 变更通知和文档变更通知。
    • 缓冲通知。

文档

  • CRUD 操作
    • 创建、读取、更新、删除文档。
    • 保存冲突处理程序。
    • 文档过期并自动清理。
    • 使用 Fleece API 直接访问二进制数据。

查询

  • 基于 Couchbase Server 的 N1QL 语言构建的查询语言。
  • 支持查询参数、解释查询结果、监听查询变化。
  • 支持值索引或全文搜索(FTS)。

复制

  • 后台任务同步本地数据库与远程服务器上的另一个数据库。
  • 支持基本认证和会话认证。
  • 支持拉取/推送过滤器。
  • 状态监听器。
  • 冲突解决回调。

Blob

  • 基于内容的 API 创建和读取。
  • 基于流的 API。

平台支持

  • Windows: 包含在包中,Beta 状态。
  • Android: 需要手动配置。
    • 可以从仓库构建共享库:https://github.com/Rudiksz/couchbase-lite-C.git,使用 feature/dart 分支或匹配版本的标签。
    • 或者从以下链接下载预构建库:https://drive.google.com/drive/folders/1qiLdB64kq-IEsp6hFgvSqG80bzaI33Jf。 将共享库放置在项目的 android/app/src/main/jniLibs/ 文件夹中。
  • iOS、macOS: 不支持。

示例与用法

main.dart 中初始化 Dart 和 C 之间的回调。

Cbl.init();

然后执行以下代码:

/// 创建或打开数据库
var db = Database('name', directory: 'path/to/directory');

// 创建文档
var doc = Document("docid", data: {'name': 'John Doe'});
db.saveDocument(doc);

// 读取不可变文档
var doc1 = db.getDocument('docid');
doc1.properties = {'foo': 'bar'}; // -> 抛出 DatabaseException

// 获取可变副本
var mutDoc = doc1.mutableCopy;
mutDoc.jsonProperties = {'foo': 'bar'}; // -> OK
db.saveDocument(mutDoc);

// 或者直接获取可变文档
var doc2 = db.getMutableDocument('testdoc3');
doc2.jsonProperties = {'foo': 'bar8'};
db.saveDocument(doc2);

// 查询

// 编译查询
final q = Query(db, 'SELECT * WHERE foo=$VALUE');

q.setParameters = {'VALUE': 'bar'};

// 可选地将其变为“实时查询”
q.addChangeListener((ResultSet results) {
    print('New query results: ');
    while(results.next()){
        final row = results.rowDict;
        print(row.json);
    }
});

// 执行查询
var results = q.execute();

// 复制器

// 创建复制器
var replicator = Replicator(
    db,
    endpointUrl: 'ws://localhost:4984/remoteDB/',
    username: 'testuser',
    password: 'password', // 或
    // 'sessionId': 'dfhfsdyf8dfenfajfoadnf83c4dfhdfad3228yrsefd',
);

// 设置状态监听器
replicator.addChangeListener((status) {
    print('Replicator status: ' + status.activityLevel.toString());
});

// 启动复制器
replicator.start();

完整的示例请参考 example 文件夹,包括 Fleece API 的使用。


完整示例代码

以下是一个完整的示例代码,展示如何使用 couchbase_lite_dart 插件。

// Copyright (c) 2020, Rudolf Martincsek. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math';

import 'package:cblc_flutter/fleece.dart';
import 'package:couchbase_lite_dart/couchbase_lite_dart.dart';
import 'package:flutter/material.dart';

void main() async {
  Cbl.init();

  var db = Database("testdb");

  print(db.isOpen);
  await Future.delayed(Duration(seconds: 1));
  var doc = Document("testdoc");
  doc.jsonProperties = testjson;
  db.saveDocument(doc);

  runApp(MaterialApp(
    title: 'Couchbase Lite Demo',
    theme: ThemeData(
      primarySwatch: Colors.blue,
    ),
    home: DatabaseView(db),
  ));
}

class DatabaseView extends StatefulWidget {
  final Database db;

  const DatabaseView(this.db, {Key key}) : super(key: key);
  [@override](/user/override)
  _DatabaseViewState createState() => _DatabaseViewState();
}

class _DatabaseViewState extends State<DatabaseView> {
  ValueNotifier<String> _explain = ValueNotifier('');
  ValueNotifier<bool> _queryChanged = ValueNotifier(false);
  ValueNotifier<bool> _resultsChanged = ValueNotifier(false);

  var _results = [];
  var _selectedDoc = '';
  static const _select = 'SELECT meta.id, * ';

  var queryText = TextEditingController(text: "");
  String prevQueryText;
  Query query;

  List documents = [1];

  [@override](/user/override)
  void initState() {
    super.initState();

    explain();
    queryText.addListener(() => _queryChanged.value = queryText.text != query?.queryString);
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('CBL - c'),
      ),
      body: Row(
        children: [
          sideBar(),
          Expanded(
            child: ValueListenableBuilder(
              builder: (_, __, ___) => FleeceView(
                docId: _selectedDoc,
                db: widget.db,
              ),
              valueListenable: _resultsChanged,
            ),
          ),
        ],
      ),
    );
  }

  sideBar() {
    return SizedBox(
      width: 300,
      child: CustomScrollView(
        slivers: [
          SliverToBoxAdapter(
              child: Text("Query: SELECT meta.id, *",
                  style: Theme.of(context).textTheme.headline5)),
          queryBox(),
          SliverToBoxAdapter(
            child: Card(
              child: ValueListenableBuilder(
                valueListenable: _explain,
                builder: (_, value, __) => Text(value),
              ),
            ),
          ),
          SliverToBoxAdapter(
              child: Text("Results",
                  style: Theme.of(context).textTheme.headline5)),
          buildDocumentList(),
        ],
      ),
    );
  }

  queryBox() {
    return SliverToBoxAdapter(
      child: Card(
        elevation: 8,
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: queryText,
                    minLines: 4,
                    maxLines: 4,
                    decoration: InputDecoration(
                      labelText: "WHERE, ORDER, LIMIT, etc",
                      alignLabelWithHint: true,
                    ),
                  ),
                ),
              ],
            ),
            Row(
              children: [
                Spacer(),
                OutlineButton(child: Text("Explain"), onPressed: explain),
                Spacer(flex: 4),
                ValueListenableBuilder(
                  builder: (_, value, __) => OutlineButton(
                    child: Text("Execute"),
                    onPressed: value ? execute : null,
                  ),
                  valueListenable: _queryChanged,
                ),
                Spacer(),
              ],
            ),
          ],
        ),
      ),
    );
  }

  buildDocumentList() {
    return ValueListenableBuilder(
      valueListenable: _resultsChanged,
      builder: (_, __, ___) =>
          (_results?.isNotEmpty ?? false)
              ? SliverList(
                  delegate: SliverChildBuilderDelegate(
                    (_, i) => Card(
                      child: ListTile(
                        selected: _results[i]['id'] == _selectedDoc,
                        title: Text(_results[i].toString()),
                        dense: true,
                        onTap: () {
                          _selectedDoc = _results[i]['id'];
                          _resultsChanged.value = !_resultsChanged.value;
                        },
                      ),
                    ),
                    childCount: _results.length,
                  ),
                )
              : SliverToBoxAdapter(child: Icon(Icons.dashboard)),
    );
  }

  updateQuery() {
    if (prevQueryText != queryText.text) {
      prevQueryText = queryText.text;
      try {
        query = Query(widget.db, _select + queryText.text);
        query.addChangeListener(updateItems);
      } on DatabaseException catch (e) {
        _explain.value = e.message;
        query = null;
      }
    }
  }

  explain() {
    updateQuery();
    _explain.value = query?.explain() ?? _explain.value;
  }

  execute() {
    updateQuery();
    if (query != null) {
      query.execute();
      _queryChanged.value = false;
    }
  }

  updateItems(List change) {
    print("************* CHANGES RECEIVED*************");
    print(change);
    _results = change ?? [];
    _resultsChanged.value = !_resultsChanged.value;
  }
}

class SliverListHeaderDelegate extends SliverPersistentHeaderDelegate {
  SliverListHeaderDelegate({
    @required this.minHeight,
    @required this.maxHeight,
    @required this.child,
    this.shrunkChild,
  });

  final double minHeight;
  final double maxHeight;
  final Widget child;
  final Widget shrunkChild;

  [@override](/user/override)
  double get minExtent => minHeight;

  [@override](/user/override)
  double get maxExtent => max(maxHeight, minHeight);

  [@override](/user/override)
  Widget build(
    BuildContext context,
    double shrinkOffset,
    bool overlapsContent,
  ) =>
      shrinkOffset < 100
          ? SizedBox.expand(child: child)
          : (shrunkChild ?? child);

  [@override](/user/override)
  bool shouldRebuild(SliverListHeaderDelegate oldDelegate) => false;
}

const testjson = '''
{
  "TESTDOC": "THIS DOCUMENT IS RECREATED EVERY TIME THE APP RUNS",
    "int": 10,
    "double": 2.2,
    "bool": true,
    "string": "hello world!",
    "list": [
        1,
        "2",
        3.3
    ],
    "map": {
        "first": [1, 2,3],
        "second": "hello again",
        "third": false,
        "dart": {"is": "cool"}
    },
    "map1": {
        "list": [
            {"first": [1, 2, 3, 4]},
            {"second": [6, 7, 8]},
            true,
            10,
            2.5
        ]
    }
}
''';

更多关于Flutter嵌入式数据库插件couchbase_lite_dart的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter嵌入式数据库插件couchbase_lite_dart的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


couchbase_lite_dart 是一个用于在 Flutter 应用程序中使用 Couchbase Lite 的插件。Couchbase Lite 是一个轻量级的嵌入式 NoSQL 数据库,适用于移动和桌面应用程序。它支持本地数据存储和同步,非常适合需要在离线状态下工作的应用程序。

以下是如何在 Flutter 项目中使用 couchbase_lite_dart 插件的步骤:

1. 添加依赖

首先,在 pubspec.yaml 文件中添加 couchbase_lite_dart 插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  couchbase_lite_dart: ^1.0.0  # 检查最新版本并替换

然后运行 flutter pub get 来安装依赖。

2. 初始化数据库

在使用 Couchbase Lite 之前,你需要初始化数据库。通常,你可以在 main.dart 文件中的 main 函数中进行初始化。

import 'package:couchbase_lite_dart/couchbase_lite_dart.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // 初始化 Couchbase Lite
  await CouchbaseLiteDart.init();
  
  runApp(MyApp());
}

3. 打开或创建数据库

你可以打开现有的数据库或创建一个新的数据库。以下是一个示例:

import 'package:couchbase_lite_dart/couchbase_lite_dart.dart';

Future<Database> openDatabase() async {
  final database = await Database.openOrCreate('my_database');
  return database;
}

4. 插入文档

你可以使用 Document 类来插入文档到数据库中:

Future<void> insertDocument(Database database) async {
  final document = MutableDocument()
    ..setString('type', 'user')
    ..setString('name', 'John Doe')
    ..setInt('age', 30);

  await database.saveDocument(document);
}

5. 查询文档

你可以使用 Query 类来查询数据库中的文档:

Future<void> queryDocuments(Database database) async {
  final query = QueryBuilder.select([SelectResult.all()])
      .from(DataSource.database(database))
      .where(Expression.property('type').equalTo(Expression.string('user')));

  final resultSet = await query.execute();
  for (final result in resultSet) {
    final document = result.toMap();
    print(document);
  }
}

6. 更新文档

你可以通过获取现有的文档并更新其内容来更新文档:

Future<void> updateDocument(Database database) async {
  final document = await database.getDocument('document_id');
  if (document != null) {
    final mutableDocument = document.toMutable();
    mutableDocument.setInt('age', 31);
    await database.saveDocument(mutableDocument);
  }
}

7. 删除文档

你可以通过文档 ID 来删除文档:

Future<void> deleteDocument(Database database) async {
  await database.deleteDocument('document_id');
}

8. 同步数据

Couchbase Lite 支持与远程数据库的同步。你可以使用 Replicator 类来设置同步:

Future<void> startReplication(Database database) async {
  final url = Uri.parse('ws://your_couchbase_server:4984/your_database');
  final target = URLEndpoint(url);
  final config = ReplicatorConfiguration(database, target)
    ..replicatorType = ReplicatorType.pushAndPull
    ..continuous = true;

  final replicator = Replicator(config);
  replicator.start();
}

9. 关闭数据库

在应用程序退出或不再需要数据库时,关闭数据库是一个好习惯:

Future<void> closeDatabase(Database database) async {
  await database.close();
}

10. 处理错误

在使用 Couchbase Lite 时,可能会遇到各种错误。确保在代码中处理这些错误:

try {
  await database.saveDocument(document);
} catch (e) {
  print('Error saving document: $e');
}
回到顶部