> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ditto.live/llms.txt
> Use this file to discover all available pages before exploring further.

# Flutter V4→V5 API Migration Guide

> Guide for migrating Flutter apps from Ditto v4 to v5.

## 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](./flutter-dql) 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.

<Accordion title="Copy AI Migration Prompt (Click to Expand)">
  ````text theme={null}
  I need help migrating a Ditto Flutter application from v4 to v5. Focus on these critical changes:

  INITIALIZATION:
  Replace `Ditto.open(identity: Identity)` with `Ditto.open(DittoConfig(...))`:
  - `DittoConfig(databaseID: String, connect: DittoConfigConnect, ...)` replaces identity types
  - `DittoConfigConnectServer(url: String)` replaces `OnlinePlaygroundIdentity` — URL is a plain `String`, NOT a `Uri`. Copy the URL from the Ditto Portal (Connect via SDK → **URL**) as-is — don't build it from the database ID.
  - `DittoConfigConnectSmallPeersOnly(privateKey: String?)` replaces `OfflinePlaygroundIdentity` and `SharedKeyIdentity`
  - `Ditto.openSync(config)` available as synchronous alternative
  - `await Ditto.init()` must be called before any Ditto usage
  - Remove `updateTransportConfig` WebSocket URL additions (cloud URL inferred from config)
  - Remove `DQL_STRICT_MODE = false` workaround (now the v5 default)
  - If your v4 app relied on `DQL_STRICT_MODE = true` (v4 default), set it BEFORE `sync.start()`:
    `await ditto.store.execute("ALTER SYSTEM SET DQL_STRICT_MODE = true")`

  SYNC:
  - `ditto.startSync()` → `ditto.sync.start()`
  - `ditto.stopSync()` → `ditto.sync.stop()`
  - `ditto.isSyncActive` → `ditto.sync.isActive`

  AUTHENTICATION:
  Set `expirationHandler` via async method after initialization — replaces `AuthenticationHandler` interface:
  - `await ditto.auth.setExpirationHandler((Ditto ditto, Duration timeUntilExpiration) { ... })`
  - `ditto.auth` is non-nullable — do NOT use `ditto.auth?.`
  - Handler receives `Ditto` (not `Authenticator`) and `Duration` (not `int`)
  - Called when `timeUntilExpiration == Duration.zero` (initial auth or expired) or `> 0` (token expiring)
  - Use `Authenticator.developmentProvider` for playground tokens
  - `ditto.auth.login()` returns `Future<AuthResponse>`. Check `response.exception`; there is no `response.error` property:
  ```dart
  await ditto.auth.setExpirationHandler((ditto, timeUntilExpiration) async {
    final response = await ditto.auth.login(
      token: 'your-token',
      provider: Authenticator.developmentProvider,
    );
    if (response.exception != null) {
      throw response.exception!;
    }
  });
  ```
  - Remove `loginWithCredentials()` — use token-based `login()` instead
  - Remove `AuthenticationHandler` class/interface

  TRANSPORT CONFIG:
  Changed from mutable to immutable with builder pattern:
  - `ditto.updateTransportConfig((builder) { builder.setAllPeerToPeerEnabled(true); })`
  - Or: `ditto.transportConfig = ditto.transportConfig.withAllPeerToPeerEnabled(true);`
  - `TransportConfig.withBigPeer(...)` removed — use `DittoConfig` server config

  PRESENCE & PEERS:
  - `PresenceGraph.remotePeers` changed from `List<Peer>` to `Set<Peer>` — use `.first` instead of `[0]`
  - `Peer.peerKeyString` → `Peer.peerKey: String` (unified)
  - `Peer.isConnectedToDittoCloud` → `Peer.isConnectedToDittoServer`
  - `Connection.peerKeyString1/peerKeyString2` → `Connection.peer1/peer2`
  - `ConnectionRequest.peerKeyString` → `ConnectionRequest.peerKey`

  OBSERVERS:
  - `registerObserver()` API is the same — callback-based with `onChange` parameter
  - Observers also expose `Stream<QueryResult> changes` for reactive use
  - No `signalNext` — backpressure handled internally by SDK

  OTHER:
  - `ditto.persistenceDirectory` → `ditto.absolutePersistenceDirectory`
  - `HttpListenConfig.websocketSync` default changed from `true` to `false`
  - `Ditto` and `Store` cannot be passed across Dart isolates
  - `DittoLogger.setLogFile()` removed
  - `SmallPeerInfoSyncScope` enum removed — use DQL `ALTER SYSTEM SET sync_scope`
  - `SiteID` and `DocumentID` are no longer exported (internal types)
  - Ditto Flutter SDK for MacOS now targets 12.0+ - if your project targets older releases you will need to raise the minimum version.
  - Ditto Flutter SDK for iOS now targets 14.0+ - if your project targets older releases you will need to raise the minimum version.
  - MacOS and iOS use CocoaPods for dependency management, because of this you will probably need to delete the Pod lock file and run `pod install` again.  This is a standard CocoaPods issue when upgrading dependencies.
  - Ditto Flutter SDK for Android minimum SDK version is now 24 (Android 7.0 Nougat). If your project targets `minSdk = 23`, you must raise it.
  - Ditto Flutter SDK for Android requires Kotlin Gradle plugin 2.0.0 or later. Kotlin 1.9 cannot read the Kotlin 2.1 metadata in the Android artifacts.

  DITTO TOOLS (only if the project uses ditto_flutter_tools):
  - Update `ditto_flutter_tools` to 3.0.0.
  - ditto_flutter_tools 3 requires `ditto_live` 5.0.1 or a later 5.x release.
  - Remove `SyncStatusHelper`, `SyncStatusView`, and `SyncStatus` usage. These APIs were removed because subscription timing did not reliably represent synchronization progress.
  ````
</Accordion>

***

## Upgrade ditto\_flutter\_tools

If the application uses `ditto_flutter_tools`, update it to `3.0.0`:

```shell theme={null}
flutter pub add 'ditto_flutter_tools:^3.0.0'
```

ditto\_flutter\_tools 3 requires `ditto_live` 5.0.1 or a later 5.x release. Remove uses of `SyncStatusHelper`, `SyncStatusView`, and `SyncStatus`; these APIs were removed because subscription timing did not reliably represent synchronization progress.

See the [ditto\_flutter\_tools releases](https://github.com/getditto/ditto_flutter_tools/releases) for the complete release notes.

***

## 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:

<CodeGroup>
  ```dart DART (v5) theme={null}
  import 'package:ditto_live/ditto_live.dart';

  // 1. Initialize the Ditto system (required before any Ditto usage)
  await Ditto.init();

  // 2. Configure
  final config = DittoConfig(
      databaseID: 'your-database-id',
      connect: DittoConfigConnectServer(
          url: 'REPLACE_ME_WITH_YOUR_URL',
      ),
  );

  // 3. Open
  final ditto = await Ditto.open(config);
  // OR synchronous: final ditto = Ditto.openSync(config);

  // 4. Authenticate (required for server connections)
  await ditto.auth.setExpirationHandler((ditto, timeUntilExpiration) async {
      final response = await ditto.auth.login(
          token: 'your-token',
          provider: Authenticator.developmentProvider,
      );
      if (response.exception != null) {
          throw response.exception!;
      }
  });

  // 5. Start sync
  ditto.sync.start();
  ```

  ```dart DART (v4) theme={null}
  import 'package:ditto_live/ditto_live.dart';

  await Ditto.init();

  final identity = OnlinePlaygroundIdentity(
      appID: 'your-app-id',
      token: 'your-token',
      customAuthUrl: 'REPLACE_ME_WITH_YOUR_URL',
      enableDittoCloudSync: false,
  );
  final ditto = await Ditto.open(identity: identity);

  ditto.updateTransportConfig((config) {
   config.setAllPeerToPeerEnabled(true);
   config.connect.webSocketUrls.add('your websocket url');
  });

  ditto.startSync();
  ```
</CodeGroup>

**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**

<Steps>
  <Step title="Replace Initialization">
    Replace `Ditto.open(identity:)` with `Ditto.open(DittoConfig)`.

    <CodeGroup>
      ```dart DART (v5) theme={null}
      final config = DittoConfig(
          databaseID: 'your-database-id',
          connect: DittoConfigConnectServer(
              url: 'REPLACE_ME_WITH_YOUR_URL',
          ),
      );
      final ditto = await Ditto.open(config);
      ditto.sync.start();
      ```

      ```dart DART (v4) theme={null}
      final ditto = await Ditto.open(
          identity: OnlinePlaygroundIdentity(
              appID: 'your-app-id',
              token: 'your-token',
              enableDittoCloudSync: false,
          ),
      );
      ditto.startSync();
      ```
    </CodeGroup>

    **Identity → DittoConfig mapping:**

    | v4.14 Identity                                           | v5.0 DittoConfig connect                             |
    | -------------------------------------------------------- | ---------------------------------------------------- |
    | `OnlinePlaygroundIdentity(appID:token:customAuthUrl:)`   | `DittoConfigConnectServer(url:)`                     |
    | `OnlineWithAuthenticationIdentity(appID:customAuthUrl:)` | `DittoConfigConnectServer(url:)` + auth handler      |
    | `OfflinePlaygroundIdentity(appID:siteID:)`               | `DittoConfigConnectSmallPeersOnly(privateKey: null)` |
    | `SharedKeyIdentity(appID:sharedKey:siteID:)`             | `DittoConfigConnectSmallPeersOnly(privateKey: key)`  |

    **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()`
  </Step>

  <Step title="Update Authentication">
    Set authentication handler after initialization.

    <CodeGroup>
      ```dart DART (v5) theme={null}
      // Set handler after Ditto.open() — note: setExpirationHandler is async
      await ditto.auth.setExpirationHandler((ditto, timeUntilExpiration) async {
          // Called for initial auth (timeUntilExpiration == Duration.zero)
          // and when token is expiring
          final response = await ditto.auth.login(
              token: 'your-token',
              provider: Authenticator.developmentProvider,
          );
          if (response.exception != null) {
              throw response.exception!;
          }
      });
      ```

      ```dart DART (v4) theme={null}
      // Auth mixed with identity (OnlineWithAuthentication)
      final identity = OnlineWithAuthenticationIdentity(
          appID: 'your-app-id',
          authenticationHandler: AuthenticationHandler(
              authenticationRequired: (authenticator) {
                  authenticator.login(
                      token: 'your-token',
                      provider: 'your-provider',
                  );
              },
              authenticationExpiringSoon: (authenticator, remaining) { },
          ),
          customAuthUrl: 'REPLACE_ME_WITH_YOUR_URL',
      );
      ```
    </CodeGroup>

    **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
  </Step>
</Steps>

***

## Additional Changes

### Android Minimum SDK

Ditto SDK v5 supports Android 7.0 (API level 24) and later. Set the minimum explicitly in your app's `android/app/build.gradle` or `android/app/build.gradle.kts`:

```kotlin theme={null}
android {
    defaultConfig {
        minSdk = 24
    }
}
```

<Note>
  With `ditto_live` 5.0.2, `flutter pub get` can succeed when the app still declares `minSdk = 23`. Dependency resolution does not confirm platform support, so check the app's Gradle configuration directly before building.
</Note>

### Android Kotlin Version

Ditto SDK v5 Android artifacts contain Kotlin 2.1 metadata. Use Kotlin Gradle plugin 2.0.0 or later; Kotlin 1.9 cannot read that metadata.

For a Flutter project that declares plugins in `android/settings.gradle`:

```groovy theme={null}
plugins {
    id "org.jetbrains.kotlin.android" version "2.0.0" apply false
}
```

### DQL Strict Mode Behavior Change

<Warning>
  **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.
</Warning>

Choose the appropriate migration path based on your current v4 configuration:

<AccordionGroup>
  <Accordion title="Currently Using DQL with DQL_STRICT_MODE=true (v4 default)">
    **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.

    <Warning>
      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.
    </Warning>

    <Note>
      To migrate to `DQL_STRICT_MODE=false` (the new v5 default), contact Ditto Customer Support for guidance.
    </Note>

    <CodeGroup>
      ```dart DART (v5 - Maintain v4 Behavior) highlight={6,7} theme={null}
      final config = DittoConfig(
          databaseID: 'your-database-id',
          connect: DittoConfigConnectServer(
              url: 'REPLACE_ME_WITH_YOUR_URL',
          ),
      );
      final ditto = await Ditto.open(config);

      // IMPORTANT: Set strict mode BEFORE starting sync or running queries
      await ditto.store.execute("ALTER SYSTEM SET DQL_STRICT_MODE = true");

      // Now safe to start sync
      ditto.sync.start();
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Currently Using DQL with DQL_STRICT_MODE=false">
    **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.

    <CodeGroup>
      ```dart DART (v5) theme={null}
      final config = DittoConfig(
          databaseID: 'your-database-id',
          connect: DittoConfigConnectServer(
              url: 'REPLACE_ME_WITH_YOUR_URL',
          ),
      );
      final ditto = await Ditto.open(config);

      // No need to set DQL_STRICT_MODE - false is now the default
      ditto.sync.start();
      ```

      ```dart DART (v4 - Your Current Setup) theme={null}
      final ditto = await Ditto.open(
          identity: OnlinePlaygroundIdentity(
              appID: 'your-app-id',
              token: 'your-token',
              enableDittoCloudSync: false,
              customAuthUrl: 'REPLACE_ME_WITH_YOUR_URL',
          ),
      );

      // You explicitly set this in v4
      await ditto.store.execute("ALTER SYSTEM SET DQL_STRICT_MODE = false");

      ditto.startSync();
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Currently Using Legacy Query Builder">
    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:
           <CodeGroup>
             ```dart DART (v4 - Enable Non-Strict Mode) theme={null}
             final ditto = await Ditto.open(
                 identity: OnlinePlaygroundIdentity(
                     appID: 'your-app-id',
                     token: 'your-token',
                     enableDittoCloudSync: false,
                     customAuthUrl: 'REPLACE_ME_WITH_YOUR_URL',
                 ),
             );

             // Set DQL_STRICT_MODE to false for Query Builder compatibility
             await ditto.store.execute("ALTER SYSTEM SET DQL_STRICT_MODE = false");

             ditto.startSync();
             ```
           </CodeGroup>

    2. **Convert all queries from Legacy Query Builder to DQL**
       See the [Flutter Legacy→DQL Migration Guide](./flutter-dql) for detailed conversion examples.

    3. **Upgrade to v5**
       No DQL configuration changes required—v5 defaults to `DQL_STRICT_MODE=false`.
  </Accordion>
</AccordionGroup>

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:**

```dart theme={null}
final config = DittoConfig(
    databaseID: 'your-database-id',
    connect: DittoConfigConnectServer(
        url: 'REPLACE_ME_WITH_YOUR_URL',
    ),
    persistenceDirectory: '/app/data/ditto',  // custom path to preserve v4 data
);
```

### 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.

<CodeGroup>
  ```dart DART (v5) theme={null}
  final observer = ditto.store.registerObserver(
      "SELECT * FROM cars WHERE color = :color",
      arguments: {"color": "red"},
      onChange: (result) {
          setState(() { _cars = result.items; });
      },
  );

  // Cleanup
  observer.cancel();

  // Alternative: use the Stream-based API
  final observer = ditto.store.registerObserver(
      "SELECT * FROM cars WHERE color = :color",
      arguments: {"color": "red"},
  );
  observer.changes.listen((result) {
      setState(() { _cars = result.items; });
  });
  ```

  ```dart DART (v4) theme={null}
  final observer = ditto.store.registerObserver(
      "SELECT * FROM cars WHERE color = :color",
      arguments: {"color": "red"},
      onChange: (result) {
          setState(() { _cars = result.items; });
      },
  );

  // Cleanup
  observer.cancel();
  ```
</CodeGroup>

**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.

```dart theme={null}
// Observers use Stream internally — backpressure is handled by the SDK
final observer = ditto.store.registerObserver(
    "SELECT * FROM cars WHERE color = :color",
    arguments: {"color": "blue"},
    onChange: (result) {
        setState(() { _cars = result.items; });
    },
);
```

If you need fine-grained flow control, use a Dart `StreamController` with `pause` / `resume` on the observer's stream.

### `PresenceGraph.remotePeers` Type Changed

<Warning>
  **Breaking Change**: `PresenceGraph.remotePeers` changed type from `List<Peer>` to `Set<Peer>`.
</Warning>

```dart theme={null}
// v4 — List, index access works
final first = graph.remotePeers[0];

// v5 — Set, use .first
final first = graph.remotePeers.first;
// Or: graph.remotePeers.toList()[0]
```

### WebSocket Sync Default Changed

<Warning>
  `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.
</Warning>

### TransportConfig Now Immutable

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

```dart theme={null}
// v5 — Using updateTransportConfig (recommended)
ditto.updateTransportConfig((builder) {
    builder.setAllPeerToPeerEnabled(true);
});

// v5 — Using builder pattern directly
final newConfig = ditto.transportConfig.toBuilder()
    ..setAllPeerToPeerEnabled(true);
ditto.transportConfig = newConfig.build();

// v5 — Using immutable copy method
ditto.transportConfig = ditto.transportConfig.withAllPeerToPeerEnabled(true);
```

```dart theme={null}
// v4 — TransportConfig was mutable
ditto.updateTransportConfig((config) {
    config.setAllPeerToPeerEnabled(true);
    config.connect.webSocketUrls.add('wss://...');
});
```

### Isolate Restriction

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

***

## API Renames

| v4.14                                                             | v5.0                                               |
| ----------------------------------------------------------------- | -------------------------------------------------- |
| `ditto.startSync()`                                               | `ditto.sync.start()`                               |
| `ditto.stopSync()`                                                | `ditto.sync.stop()`                                |
| `ditto.isSyncActive`                                              | `ditto.sync.isActive`                              |
| `Peer.peerKeyString` + `Peer.peerKey: Uint8List`                  | `Peer.peerKey: String` (unified)                   |
| `Peer.isConnectedToDittoCloud`                                    | `Peer.isConnectedToDittoServer`                    |
| `Connection.peerKeyString1` / `peerKeyString2`                    | `Connection.peer1` / `Connection.peer2`            |
| `ConnectionRequest.peerKeyString`                                 | `ConnectionRequest.peerKey`                        |
| `ditto.persistenceDirectory`                                      | `ditto.absolutePersistenceDirectory`               |
| `SmallPeerInfo.syncScope: SmallPeerInfoSyncScope`                 | DQL: `ALTER SYSTEM SET sync_scope = '...'`         |
| `SmallPeerInfoSyncScope` enum                                     | Removed — use DQL                                  |
| `AuthenticationHandler` interface                                 | `AuthenticationExpirationHandler` typedef          |
| `Authenticator.loginWithCredentials(username:password:provider:)` | Removed — use token-based `login(token:provider:)` |
| `TransportConfig.withBigPeer(...)`                                | Removed — use `DittoConfig` server configuration   |
| `DittoLogger.setLogFile(...)`                                     | Removed — file logging no longer supported         |
| `HttpListenConfig.staticContentPath`                              | Removed — no replacement                           |
| `PresenceGraph.remotePeers: List<Peer>`                           | `PresenceGraph.remotePeers: Set<Peer>`             |
| `SiteID` class                                                    | Not exported (internal type)                       |
| `DocumentID` class                                                | Not exported (internal type)                       |

***

## Key Initialization Changes

| v4.14                                             | v5.0                                    | Notes                                           |
| ------------------------------------------------- | --------------------------------------- | ----------------------------------------------- |
| `Ditto.open(identity: Identity)`                  | `Ditto.open(DittoConfig)`               | Positional parameter, `DittoConfig` has default |
| `Identity` subclasses                             | `DittoConfigConnect*` subclasses        | See identity mapping table                      |
| `OnlinePlaygroundIdentity.customAuthUrl: String?` | `DittoConfigConnectServer(url: String)` | URL is now a `String`, not `Uri`                |
| `OnlinePlaygroundIdentity.enableDittoCloudSync`   | Removed                                 | Cloud sync is configured via connect mode       |

## Authentication API Changes

| v4.14                                                             | v5.0                                                 | Notes                                                                    |
| ----------------------------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------ |
| `AuthenticationHandler` interface                                 | `AuthenticationExpirationHandler` typedef            | `void Function(Ditto, Duration)`                                         |
| `authenticationRequired(Authenticator)`                           | `expirationHandler` called with `Duration.zero`      | Single handler replaces two callbacks                                    |
| `authenticationExpiringSoon(Authenticator, int)`                  | `expirationHandler` called with remaining `Duration` | Combined into one handler                                                |
| `authenticator.loginWithCredentials(username:password:provider:)` | Removed                                              | Use `login(token:provider:)`                                             |
| `authenticator.login(token:provider:)`                            | `ditto.auth.login(token:provider:)`                  | Returns `AuthResponse`; throw `response.exception` when it is not `null` |
| Set handler via identity constructor                              | `await ditto.auth.setExpirationHandler(...)`         | Async method, set after `Ditto.open()`                                   |

***

## APIs Removed Without Replacement

| Removed API                              | Notes                                          |
| ---------------------------------------- | ---------------------------------------------- |
| `Connection.approximateDistanceInMeters` | Peer distance estimation removed               |
| `DittoLogger.setLogFile(...)`            | File logging removed entirely                  |
| `HttpListenConfig.staticContentPath`     | Static HTTP file serving removed               |
| `TransportConfig.withBigPeer(...)`       | Use `DittoConfig` server config instead        |
| `SmallPeerInfoSyncScope` enum            | Sync scope is now a DQL `ALTER SYSTEM` setting |
| `ManualIdentity`                         | Manual certificate identity removed            |

***

## 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:)`
* [ ] Check `AuthResponse.exception` after every `login()` call; do not use the nonexistent `AuthResponse.error`
* [ ] Use `Authenticator.developmentProvider` for dev tokens
* [ ] Remove `?.` on `ditto.auth` — it is non-nullable in v5

### Breaking Changes

* [ ] Set Android `minSdk` to 24 or later, even if `flutter pub get` succeeds with 23
* [ ] Upgrade the Android Kotlin Gradle plugin to 2.0.0 or later
* [ ] 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)

***


## Related topics

- [C++ V4→V5 API Migration Guide](/sdk/latest/migration-guides/cpp-v4.md)
- [C# V4→V5 API Migration Guide](/sdk/latest/migration-guides/c-sharp-v4.md)
- [Rust V4→V5 API Migration Guide](/sdk/latest/migration-guides/rust-v4.md)
