Skip to main content

Overview

This guide covers the essential changes needed to migrate your Ditto Flutter app from v4 to v5. The main architectural shift is moving from identity-based initialization to DittoConfig, plus several breaking type changes. Also required: v5 uses DQL (Ditto Query Language) for all data operations. See the DQL Migration Guide for query migration steps.

AI Agent Prompt

Use this prompt when working with an AI coding assistant to migrate your Ditto Flutter app from v4 to v5.

Ditto Instance Initialization

v5 separates initialization into four distinct phases for better clarity and control. Note that Ditto.init() is a prerequisite step that must be called before any Ditto usage:
These changes provide:
  • Clear separation of connection, initialization, and authentication concerns
  • Async initialization prevents blocking the main thread
  • No workarounds needed (transport config, DQL strict mode, v3 sync disable)
  • Type-safe configuration with compile-time validation
Initialization Migration Steps
1

Replace Initialization

Replace Ditto.open(identity:) with Ditto.open(DittoConfig).
Identity β†’ DittoConfig mapping:Key changes:
  • Use DittoConfig with databaseID instead of identity types
  • appID β†’ databaseID
  • Connect mode selected via DittoConfigConnect* subclasses
  • DittoConfigConnectServer.url takes a String, not a Uri
  • New: Ditto.openSync(config) synchronous alternative
  • ditto.sync.start() instead of ditto.startSync()
2

Update Authentication

Set authentication handler after initialization.
Key changes:
  • AuthenticationHandler interface removed β€” use AuthenticationExpirationHandler typedef
  • Set handler via await ditto.auth.setExpirationHandler(...) (async method, not property assignment)
  • ditto.auth is non-nullable in v5 (no ?. needed)
  • Handler signature: void Function(Ditto ditto, Duration timeUntilExpiration) β€” receives Ditto instance and Duration, not Authenticator and int
  • Authenticator.loginWithCredentials(username:password:provider:) removed β€” use token-based login(token:provider:)
  • Authenticator.developmentProvider is the dev provider constant

Additional Changes

DQL Strict Mode Behavior Change

Breaking Change: v5 defaults to DQL_STRICT_MODE=false, which fundamentally changes how DQL queries behave.
  • v4 default: Objects treated as REGISTER (whole-object replacement)
  • v5 default: Objects treated as MAP (field-level merging)
This affects the behavior of all DQL SELECT, INSERT, and UPDATE operations.
Choose the appropriate migration path based on your current v4 configuration:
If you’re currently using the v4 default (DQL_STRICT_MODE=true), you must explicitly set strict mode to true in v5 before starting sync or executing any queries.
Failing to set strict mode will cause objects to merge at the field level instead of replacing entirely, which can result in unexpected data behavior and perceived data loss.
To migrate to DQL_STRICT_MODE=false (the new v5 default), contact Ditto Customer Support for guidance.
If you explicitly set DQL_STRICT_MODE=false in v4, no changes are required.v5 uses DQL_STRICT_MODE=false as the default, so your existing DQL queries will behave identically. You can upgrade freely.Optional: You can remove the explicit ALTER SYSTEM SET DQL_STRICT_MODE = false statement in v5 since this is now the default behavior.
The Legacy Query Builder has been removed in v5. All queries must be converted to DQL before upgrading.Good news: Legacy Query Builder functionality has 1:1 support with DQL_STRICT_MODE=false (the v5 default), making migrations straightforward.Migration Steps:
  1. In v4: Set DQL_STRICT_MODE=false after initialization:
  2. Convert all queries from Legacy Query Builder to DQL See the Flutter Legacy→DQL Migration Guide for detailed conversion examples.
  3. Upgrade to v5 No DQL configuration changes requiredβ€”v5 defaults to DQL_STRICT_MODE=false.
For additional guidance or questions, contact Ditto Customer Support.

Default Persistence Directory

v5 includes the database ID in the default directory name: ditto-{databaseID} instead of ditto. This is for only new databases created in v5. v5 does not migrate existing databases to the new structure. Provide custom persistence directory:

Observer Changes

v4 and v5 both use callback-based observers. The core registerObserver API pattern is the same in both versions, returning a StoreObserver that you retain and call .cancel() on for cleanup.
Key changes:
  • The observer lifecycle pattern (retain, .cancel(), cleanup) is the same as v4
  • v5 observers expose a Stream<QueryResult> changes property for reactive use
  • StoreObserver.queryString and queryArguments properties available for introspection

Back Pressure

The Flutter SDK handles back pressure internally through its Stream<QueryResult>-based observer model. There is no signalNext function to call β€” the SDK manages update delivery automatically.
If you need fine-grained flow control, use a Dart StreamController with pause / resume on the observer’s stream.

PresenceGraph.remotePeers Type Changed

Breaking Change: PresenceGraph.remotePeers changed type from List<Peer> to Set<Peer>.

WebSocket Sync Default Changed

HttpListenConfig.websocketSync now defaults to false (was true in v4). If your app relied on WebSocket sync being enabled automatically, you must now enable it explicitly.

TransportConfig Now Immutable

TransportConfig and all its sub-types are now @immutable. Use the builder pattern or the updateTransportConfig convenience method:

Isolate Restriction

Ditto and Store are marked @pragma("vm:isolate-unsendable"). They cannot be passed across Dart isolates.

API Renames


Key Initialization Changes

Authentication API Changes


APIs Removed Without Replacement


New APIs in v5

  • Ditto.init() β€” required static initialization before using any Ditto features (throws if not called)
  • DittoConfig / DittoConfigConnectServer / DittoConfigConnectSmallPeersOnly β€” new configuration hierarchy replacing all identity types
  • Ditto.openSync(config) β€” synchronous constructor alternative to async Ditto.open(config)
  • Ditto.defaultRootDirectory β€” static helper for default persistence path
  • ditto.config β€” access the config object used to open the instance
  • AuthenticationExpirationHandler typedef β€” replaces AuthenticationHandler interface
  • Authenticator.developmentProvider β€” constant for development auth provider
  • Authenticator.setExpirationHandler() β€” async method to set the expiration handler
  • SyncSubscription.queryArgumentsCborBytes / queryArgumentsJsonString β€” inspect subscription query arguments
  • Builder types for all transport config sub-types (TransportConfigBuilder, PeerToPeerBuilder, etc.)
  • TransportConfig.withAllPeerToPeerEnabled(bool) β€” immutable copy method for enabling P2P transports
  • Ditto and Store marked @pragma("vm:isolate-unsendable") for safety

Migration Checklist

Initialization

  • Ensure await Ditto.init() is called before any Ditto usage
  • Replace Ditto.open(identity:) with Ditto.open(DittoConfig(...))
  • Create DittoConfig with databaseID and connect mode
  • Update identity types to DittoConfigConnect* variants
  • Use String URLs (not Uri) for DittoConfigConnectServer
  • Remove updateTransportConfig calls for WebSocket URLs (cloud URL inferred from config)
  • Set DQL_STRICT_MODE=true BEFORE sync.start() if maintaining v4 behavior
  • Update startSync() β†’ sync.start()

Authentication

  • Replace AuthenticationHandler interface with AuthenticationExpirationHandler typedef
  • Set handler via await ditto.auth.setExpirationHandler(...) after Ditto.open()
  • Update handler signature: receives (Ditto ditto, Duration timeUntilExpiration), not (Authenticator, int)
  • Replace loginWithCredentials(username:password:provider:) with login(token:provider:)
  • Use Authenticator.developmentProvider for dev tokens
  • Remove ?. on ditto.auth β€” it is non-nullable in v5

Breaking Changes

  • Update remotePeers[0] β†’ remotePeers.first (type changed from List to Set)
  • Check HttpListenConfig.websocketSync β€” must be explicitly enabled now (default is false)
  • Update TransportConfig usage to builder pattern: .toBuilder()...build() or updateTransportConfig()
  • Update peerKeyString β†’ peerKey
  • Update isConnectedToDittoCloud β†’ isConnectedToDittoServer
  • Update persistenceDirectory β†’ absolutePersistenceDirectory
  • Do not pass Ditto or Store across isolates
  • Set persistenceDirectory in DittoConfig if maintaining v4 directory structure

Verification

  • Build compiles with zero errors
  • Observers update data immediately
  • Authentication works before sync starts
  • No remotePeers index access errors
  • WebSocket sync works if needed (explicitly enabled)