Migration: v3 to v4
Overview​
Version 4 replaces the multi-package architecture with a single cbl package
powered by
Dart native assets. Native
libraries are now downloaded and compiled automatically by the Dart build system
— no manual setup or separate platform packages are needed.
Package changes​
| v3 Package | v4 Replacement |
|---|---|
cbl | cbl (unchanged) |
cbl_dart | cbl (merged) |
cbl_flutter | cbl (merged) |
cbl_flutter_platform_interface | Removed (no longer needed) |
cbl_flutter_ce | Removed (edition configured via pubspec.yaml) |
cbl_flutter_ee | Removed (edition configured via pubspec.yaml) |
cbl_libcblite_api | Removed (handled by build hook) |
cbl_libcblitedart_api | Removed (handled by build hook) |
Dependency changes​
Before (v3) — Flutter​
dependencies:
cbl: ...
cbl_flutter: ...
cbl_flutter_ce: ... # or cbl_flutter_ee
Before (v3) — Standalone Dart​
dependencies:
cbl: ...
cbl_dart: ...
After (v4) — Both Flutter and Standalone Dart​
dependencies:
cbl: ^4.0.0-dev.6
A single dependency is all that is needed for both Flutter and standalone Dart apps.
Edition configuration​
In v3, the edition was selected by choosing a package (cbl_flutter_ce vs
cbl_flutter_ee) or by passing it to CouchbaseLiteDart.init().
In v4, configure the edition in your package pubspec.yaml:
hooks:
user_defines:
cbl:
edition: enterprise # or community (default)
If you are using a Dart pub workspace, hooks.user_defines are read from the
workspace root pubspec.yaml, so put this configuration there instead.
If no edition is specified, the Community edition is used by default.
Initialization changes​
Before (v3) — Flutter​
import 'package:cbl_flutter/cbl_flutter.dart';
await CouchbaseLiteFlutter.init();
Before (v3) — Standalone Dart​
import 'package:cbl_dart/cbl_dart.dart';
await CouchbaseLiteDart.init(edition: Edition.enterprise);
After (v4)​
Couchbase Lite no longer requires explicit initialization before use.
In v4, Database.defaultDirectory automatically resolves to the platform's
standard app data directory for Flutter apps (see
Default database directory
for the per-platform locations). For standalone Dart processes, the current
working directory is used.
If you previously passed filesDir to CouchbaseLite.init to customize the
default database directory, use Database.defaultDirectory instead.
import 'package:cbl/cbl.dart';
Database.defaultDirectory = '/path/to/files';
Vector search initialization​
In v3, vector search was automatically enabled during CouchbaseLite.init if
the extension was available and the system supported it. The init methods
accepted an autoEnableVectorSearch parameter to control this behavior.
In v4, vector search is never enabled automatically.
autoEnableVectorSearch has been removed. You must explicitly call
Extension.enableVectorSearch before opening a database that uses vector
search:
final status = Extension.enableVectorSearch();
if (status != VectorSearchStatus.enabled) {
// Handle the case where vector search could not be enabled.
}
Extension.enableVectorSearch now returns a VectorSearchStatus
enum instead of throwing an exception when vector search is unavailable. See
Working with Vector Search for
details on the status values.
Vector search configuration​
In v3, vector search setup was manual. In v4, enable it in pubspec.yaml:
hooks:
user_defines:
cbl:
edition: enterprise
vector_search: true
Vector search requires the Enterprise edition and is supported on ARM64 and x86-64.
FullTextIndex.langauge renamed to language​
The langauge method on FullTextIndex was misspelled. It has been renamed
to language:
// Before (v3)
final index = FullTextIndex([FullTextIndexItem.property('text')])
.langauge(FullTextLanguage.english);
// After (v4)
final index = FullTextIndex([FullTextIndexItem.property('text')])
.language(FullTextLanguage.english);
pinnedServerCertificate and trustedRootCertificates type changes​
The pinnedServerCertificate and trustedRootCertificates fields on
ReplicatorConfiguration have changed types from Uint8List? to
CryptoData? and PemData? respectively.
// Before (v3)
final config = ReplicatorConfiguration(
target: urlEndpoint,
pinnedServerCertificate: certBytes, // Uint8List
trustedRootCertificates: pemBytes, // Uint8List
);
// After (v4)
final config = ReplicatorConfiguration(
target: urlEndpoint,
pinnedServerCertificate: DerData(certBytes), // or PemData(certString)
trustedRootCertificates: PemData(pemString),
);
MutableDocument constructor changes​
The MutableDocument() and MutableDocument.withId() constructors have been
merged into a single constructor where id is now a named parameter:
// Before (v3)
final doc1 = MutableDocument();
final doc2 = MutableDocument({'key': 'value'});
final doc3 = MutableDocument.withId('my-id');
final doc4 = MutableDocument.withId('my-id', {'key': 'value'});
// After (v4)
final doc1 = MutableDocument({});
final doc2 = MutableDocument({'key': 'value'});
final doc3 = MutableDocument(id: 'my-id', {});
final doc4 = MutableDocument(id: 'my-id', {'key': 'value'});
The data parameter is now required and non-nullable. For an empty document,
pass {}. The MutableDocument.withId constructor has been removed. When
providing an id, the convention is to pass it before the data.
LogFileConfiguration.maxRotateCount renamed to maxKeptFiles​
The maxRotateCount property on LogFileConfiguration has been renamed to
maxKeptFiles. Update any references accordingly:
// Before (v3)
final config = LogFileConfiguration(
directory: logDir,
maxRotateCount: 5,
);
// After (v4)
final config = LogFileConfiguration(
directory: logDir,
maxKeptFiles: 5,
);
Query boolean expressions return booleans instead of integers​
In v3, boolean-producing query expressions (comparisons, logical operators, null
checks, like(), between(), in_(), is_(), Meta.isDeleted,
quantifiers) returned 1 and 0 as integers.
In v4, these expressions return native true and false boolean values. If
your code checks query results for == 1 or == 0 instead of using them as
booleans, you will need to update those checks.
Revision IDs are no longer deterministic​
CBL 4 changed its revision ID generation algorithm. Revision IDs are no longer derived deterministically from document content. Code that hardcodes or predicts revision IDs based on document content will break.
Conflict resolver exception handling​
In v3, when a custom ConflictResolver threw an exception, the native layer
would propagate a C++ exception across library boundaries. This was undefined
behavior and caused crashes on some platforms (notably Windows).
In v4, when a ConflictResolver throws:
- The exception is reported on the Dart side as an unhandled error in the current zone (unchanged).
- On the native side, the
CBLDefaultConflictResolveris called as a fallback instead of propagating a C++ exception. This resolver uses timestamp-based logic: the document with the newer timestamp wins, or if either side deleted the document, the deletion wins.
This means a throwing conflict resolver no longer causes cblite to report a
conflict resolution error — the conflict is silently resolved using the default
strategy. Applications that relied on detecting conflict resolver failures
should catch exceptions within their ConflictResolver.resolve
implementation and handle them explicitly.
DefaultConflictResolver uses timestamp-based logic​
In v3, the DefaultConflictResolver used revision ID string comparison to pick
the winning document during replication conflicts. Since v4 revision IDs are no
longer deterministic (see above), this strategy is no longer meaningful.
In v4, DefaultConflictResolver uses HLC timestamp comparison instead, matching
the behavior of the native CBLDefaultConflictResolver: the document with the
later timestamp wins, and if either side is a deletion, the deletion wins. This
is the same strategy used as the fallback when a custom conflict resolver throws
an exception.
DefaultConflictResolver is now also exported from the public API, so you can
use it directly in a custom ConflictResolver to apply the default strategy
for some documents:
class MyConflictResolver extends ConflictResolver {
const MyConflictResolver();
FutureOr<Document?> resolve(Conflict conflict) {
if (shouldUseCustomStrategy(conflict)) {
return customResolve(conflict);
}
// Fall back to the default timestamp-based strategy.
return const DefaultConflictResolver().resolve(conflict);
}
}
If your code relied on revision ID ordering to predict conflict outcomes, update it to account for the new timestamp-based behavior.
Conflict handling in saveDocument and deleteDocument​
In v3, Collection.saveDocument and Collection.deleteDocument
returned false when a conflict occurred with
ConcurrencyControl.failOnConflict.
In v4, these methods return void and throw a DatabaseException with
DatabaseErrorCode.conflict instead. This aligns with other Couchbase
Lite SDKs and makes conflicts harder to accidentally ignore.
The same applies to Collection.deleteTypedDocument and
SaveTypedDocument.withConcurrencyControl.
// Before (v3)
final saved = await collection.saveDocument(
doc,
ConcurrencyControl.failOnConflict,
);
if (!saved) {
// handle conflict
}
// After (v4)
try {
await collection.saveDocument(
doc,
ConcurrencyControl.failOnConflict,
);
} on DatabaseException catch (e) {
if (e.code == DatabaseErrorCode.conflict) {
// handle conflict
} else {
rethrow;
}
}
Removed deprecated Database APIs​
In v3, Database provided document operations, change listeners, and index
management directly. These were deprecated in favor of the collection-based API.
In v4, these deprecated methods have been removed. Use the equivalent methods on
Collection (typically via defaultCollection) instead.
Document operations​
// Before (v3)
final doc = await db.document('doc-id');
await db.saveDocument(mutableDoc);
await db.deleteDocument(doc);
await db.purgeDocumentById('doc-id');
await db.setDocumentExpiration('doc-id', expiry);
final count = await db.count;
// After (v4)
final collection = await db.defaultCollection;
final doc = await collection.document('doc-id');
await collection.saveDocument(mutableDoc);
await collection.deleteDocument(doc);
await collection.purgeDocumentById('doc-id');
await collection.setDocumentExpiration('doc-id', expiry);
final count = collection.count;
The following Database methods have been removed:
document,typedDocument,operator []saveDocument,saveDocumentWithConflictHandler,saveTypedDocumentdeleteDocument,deleteTypedDocumentpurgeDocument,purgeTypedDocument,purgeDocumentByIdsetDocumentExpiration,getDocumentExpirationcount
Change listeners​
// Before (v3)
db.addChangeListener((change) { /* DatabaseChange */ });
db.addDocumentChangeListener('doc-id', (change) { ... });
db.changes(); // Stream<DatabaseChange>
db.documentChanges('doc-id');
// After (v4)
final collection = await db.defaultCollection;
collection.addChangeListener((change) { /* CollectionChange */ });
collection.addDocumentChangeListener('doc-id', (change) { ... });
collection.changes(); // Stream<CollectionChange>
collection.documentChanges('doc-id');
The DatabaseChange class and DatabaseChangeListener typedef have been
removed. Use CollectionChange and CollectionChangeListener instead.
Index management​
// Before (v3)
await db.createIndex('myIndex', index);
await db.deleteIndex('myIndex');
final names = await db.indexes;
// After (v4)
final collection = await db.defaultCollection;
await collection.createIndex('myIndex', index);
await collection.deleteIndex('myIndex');
final names = await collection.indexes;
Removed deprecated Replicator APIs​
ReplicatorConfiguration changes​
The database parameter and all collection-level options on
ReplicatorConfiguration have been removed. Use
CollectionConfiguration and ReplicatorConfiguration.addCollection
instead.
// Before (v3)
final config = ReplicatorConfiguration(
database: db,
target: urlEndpoint,
channels: ['channel1'],
pushFilter: myPushFilter,
conflictResolver: myResolver,
);
// After (v4)
final collection = await db.defaultCollection;
final config = ReplicatorConfiguration(target: urlEndpoint);
config.addCollection(collection, CollectionConfiguration(
channels: ['channel1'],
pushFilter: myPushFilter,
conflictResolver: myResolver,
));
The following ReplicatorConfiguration properties have been removed:
databasechannels,documentIdspushFilter,typedPushFilter,pullFilter,typedPullFilterconflictResolver,typedConflictResolver
Pending document APIs​
// Before (v3)
final ids = await replicator.pendingDocumentIds;
final isPending = await replicator.isDocumentPending('doc-id');
// After (v4)
final ids = await replicator.pendingDocumentIdsInCollection(collection);
final isPending = await replicator.isDocumentPendingInCollection('doc-id', collection);
Removed DataSource.database​
Use DataSource.collection instead:
// Before (v3)
final query = const QueryBuilder()
.select(SelectResult.all())
.from(DataSource.database(db));
// After (v4)
final collection = await db.defaultCollection;
final query = const QueryBuilder()
.select(SelectResult.all())
.from(DataSource.collection(collection));
Removed InvalidJsonException​
The deprecated InvalidJsonException class has been removed. Use
FleeceException instead.
Removed TracingDelegate API​
The TracingDelegate API and related tracing types (TracedOperation,
ChannelCallOp, NativeCallOp, etc.) have been removed from the public API.
This API will be replaced by an OpenTelemetry-based instrumentation API in a
future release.
If you were using TracingDelegate directly, remove any references to it. The
cbl_sentry package continues to provide Sentry integration for Couchbase Lite
operations and does not require any changes.
Dart SDK requirement​
Version 4 requires Dart SDK ^3.10.0 due to the use of Dart native assets.
Native library management​
In v3, native libraries had to be downloaded or built manually (for standalone
Dart via CouchbaseLiteDart.init, for Flutter via prebuilt platform packages).
In v4, the build hook at packages/cbl/hook/build.dart handles everything
automatically:
- Downloads
libcblitefor the target platform. - Compiles
libcblitedartfrom source. - Optionally downloads the vector search extension.
No manual steps are required.
Step-by-step migration checklist​
- Update Dart SDK to
^3.10.0. - Remove old packages from your
pubspec.yaml:cbl_dartcbl_fluttercbl_flutter_ceorcbl_flutter_ee
- Keep or add the
cbldependency. - Configure edition (if using Enterprise) in
pubspec.yamlunderhooks.user_defines.cbl. In a Dart pub workspace, use the workspace rootpubspec.yaml. - Update imports:
- Replace
import 'package:cbl_flutter/cbl_flutter.dart'withimport 'package:cbl/cbl.dart'. - Replace
import 'package:cbl_dart/cbl_dart.dart'withimport 'package:cbl/cbl.dart'.
- Replace
- Remove initialization: Remove calls to
CouchbaseLiteFlutter.init()orCouchbaseLiteDart.init(...). Initialization is no longer required. If you customized the default database directory via thefilesDirparameter, useDatabase.defaultDirectoryinstead. - Enable vector search (if needed) via
pubspec.yamlconfig and an explicit call toExtension.enableVectorSearch. - Rename
langaugetolanguageonFullTextIndex. - Update certificate fields on
ReplicatorConfiguration— wrappinnedServerCertificatebytes withDerDataorPemData, andtrustedRootCertificatesbytes withPemData. - Update
MutableDocumentconstructors — replaceMutableDocument()withMutableDocument({})andMutableDocument.withId(id, data)withMutableDocument(id: id, data). - Rename
maxRotateCounttomaxKeptFilesin anyLogFileConfigurationusage. - Update query result checks that compare boolean expressions against
1or0— they now returntrue/false. - Remove hardcoded revision IDs — revision IDs are no longer deterministic.
- Review conflict resolution logic —
DefaultConflictResolvernow uses timestamp comparison instead of revision ID comparison. - Update conflict handling —
saveDocumentanddeleteDocumentwithfailOnConflictnow throwDatabaseExceptioninstead of returningfalse. - Migrate Database document/index/listener APIs to use
defaultCollectionmethods instead. - Migrate ReplicatorConfiguration — replace
databaseparameter withaddCollection, movechannels/documentIds/filters/resolvers intoCollectionConfiguration. - Migrate Replicator pending document APIs — replace
pendingDocumentIds/isDocumentPendingwithpendingDocumentIdsInCollection/isDocumentPendingInCollection. - Replace
DataSource.database(db)withDataSource.collection(collection). - Replace
InvalidJsonExceptionwithFleeceException. - Remove
TracingDelegateusage — the tracing API has been removed from the public API. - Run your app — native libraries will be built automatically on first run.