Skip to content

Client Consumption

This guide starts after Server setup. It uses that guide’s synchro_quickstart database, alice identity, user:alice scope, and notes table.

Use local artifacts before Synchro 0.4.0-rc.4 is published. Use the published commands only after the matching release artifact is available. Both paths use Synchro 0.4.0-rc.4 and the same client APIs.

Build local artifacts from this repository:

Terminal window
make local-consumer-artifacts

This creates:

  • Apple package files in dist/local-consumer/apple/Synchro
  • a Kotlin Maven repository in dist/local-consumer/maven
  • dist/local-consumer/npm/trainstar-synchro-react-native-0.4.0-rc.4.tgz

Do not link raw source directories into another application. The staged artifacts are the supported pre-release path.

Surface Local artifact Published artifact after release
Swift local package path Git tag v0.4.0-rc.4
CocoaPods local pod path Git tag v0.4.0-rc.4
Kotlin local Maven repository Maven Central fit.trainstar:synchro:0.4.0-rc.4
React Native local .tgz npm @trainstar/synchro-react-native@0.4.0-rc.4
  • Swift clients require Swift 6 and iOS 17 or later.
  • Kotlin clients require Android API 24 or later and JDK 17. Follow the Android toolchain upgrade.
  • React Native requires >=0.82.1 <0.89.0 and React >=19.1.1 <20.0.0.
  • React Native 0.88.0 requires React 19.3.0 and Node ^22.13.0 || ^24.3.0 || >=26.0.0.
  • React Native 0.82.1 iOS applications require the fmt backport with Xcode 27.
  • Applications built with Xcode 27 must adopt the UIKit scene lifecycle.
  • The repository React Native development toolchain is pinned to Node 22.20.0 in clients/react-native/.nvmrc. This pin is for repository development. It does not raise the published package floor.
  • Each client needs a writable private database path, a stable client ID, and an authentication callback that returns an access token for alice.

The tutorial adapter uses http://127.0.0.1:8091. Use that address only for the local development adapter. Android emulators must use http://10.0.2.2:8091 instead. A physical device must use a reachable HTTPS endpoint, not a host loopback address.

The Apple SDK and React Native iOS bridge compile in Swift 6 language mode. Native change, query, status, sync-event, and conflict callbacks require @Sendable closures.

Capture immutable Sendable values or synchronized Sendable owners in these callbacks. Use the owning actor to change actor-isolated state. Keep each callback subscription until observation ends, then call cancel(). Call close() when the client is no longer required.

Add the package to Package.swift after Synchro v0.4.0-rc.4 is published:

dependencies: [
.package(
url: "https://github.com/trainstar/synchro.git",
exact: "0.4.0-rc.4"
)
],
targets: [
.target(
name: "NotesApp",
dependencies: [.product(name: "Synchro", package: "Synchro")]
)
]

For local development, replace the remote dependency with:

.package(path: "/absolute/path/to/synchro/dist/local-consumer/apple/Synchro")

Open a client from a writable Application Support path. The function performs the first sync before it returns:

import Foundation
import Synchro
enum NotesSetupError: Error {
case unexpectedServerNote
}
func openNotesClient(
accessToken: String,
clientID: String,
databaseFileName: String,
serverURL: URL
) async throws -> SynchroClient {
let directory = try FileManager.default.url(
for: .applicationSupportDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true
)
let databaseURL = directory.appendingPathComponent(databaseFileName)
let client = try SynchroClient(
config: SynchroConfig(
dbPath: databaseURL.path,
serverURL: serverURL,
authProvider: { accessToken },
clientID: clientID,
appVersion: "0.3.0"
)
)
do {
let noteID = "00000000-0000-4000-8000-000000000001"
try await client.start()
try await client.syncNow()
guard let initial = try client.query(
"SELECT id, body FROM notes WHERE id = ?",
params: [noteID]
).first,
initial["id"] as? String == noteID else {
throw NotesSetupError.unexpectedServerNote
}
await client.stop()
try client.execute(
"UPDATE notes SET body = ? WHERE id = ?",
params: ["Edited offline", noteID]
)
try await client.start()
try await client.syncNow()
guard let updated = try client.query(
"SELECT body FROM notes WHERE id = ?",
params: [noteID]
).first,
updated["body"] as? String == "Edited offline" else {
throw NotesSetupError.unexpectedServerNote
}
return client
} catch {
try? await client.close()
throw error
}
}

The application obtains accessToken from its identity provider. Do not put a JWT in source code. Pass http://127.0.0.1:8091 as serverURL only for the local tutorial adapter. When the application exits, call try await client.close().

RC4 uses Kotlin 2.4.21 with language and API mode 2.4, AGP 9.4.1, and Gradle 9.8.2. The published native library contains Kotlin 2.4 metadata. Use this compiler setup for native and React Native Android applications. An older Kotlin compiler cannot be assumed to read that metadata. The runtime floor remains Android API 24. Keep JVM 1.8 bytecode and core library desugaring for that floor.

  1. Install JDK 17 and Android API 37.
  2. Set application compileSdk and targetSdk to 37.
  3. Select AGP 9.4.1 and Kotlin compiler 2.4.21 in the root build configuration.
  4. Set the Gradle wrapper properties to these exact values:
distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.2-bin.zip
distributionSha256Sum=21bd5da2de49b642904077f38c0efdc7778e92b3ed0889392dca61b97e389607

Native Android hosts use AGP’s built-in Kotlin integration. The root buildscript selects org.jetbrains.kotlin:kotlin-gradle-plugin:2.4.21 on its classpath. Do not also apply org.jetbrains.kotlin.android to a built-in Kotlin Android module. The native consumer configuration shows this setup.

React Native hosts use the current upstream template’s external Kotlin integration. Set these properties in android/gradle.properties:

android.builtInKotlin=false
android.newDsl=false

Set kotlinVersion = "2.4.21", compileSdkVersion = 37, and targetSdkVersion = 37 in android/build.gradle. Select com.android.tools.build:gradle:9.4.1 and org.jetbrains.kotlin:kotlin-gradle-plugin:2.4.21 in its buildscript dependencies. Keep the application’s external Kotlin plugin application.

For RN 0.82.1, enable buildFeatures { resValues = true } in the application android block. Replace getDefaultProguardFile("proguard-android.txt") with getDefaultProguardFile("proguard-android-optimize.txt") in that application. The current RN 0.88.0 template already contains those application settings.

After each fresh package installation, apply the delivered RNGP correction before the Android build:

Terminal window
node node_modules/@trainstar/synchro-react-native/tooling-patches/apply.cjs rngp

The command accepts RN 0.82.1 with RNGP 0.82.1, or RN 0.88.0 with RNGP 0.88.0. It verifies exact source hashes and rejects any other pair. The package’s broader peer-dependency range does not expand this toolchain-correction boundary. The support matrix remains RN 0.82.1 and the current 0.88.x track. Each new version needs its actual plugin sources and consumer validation before this correction can accept it. Do not change package versions or bypass hash checks to apply another version’s patch.

The RNGP command changes installed plugin sources only. It does not make the required application build-file changes above. The consumer preparation contains the complete tested host setup.

Add the published artifact after Synchro 0.4.0-rc.4 is available:

repositories {
mavenCentral()
}
dependencies {
implementation("fit.trainstar:synchro:0.4.0-rc.4")
}

For local development, add the staged repository before mavenCentral():

repositories {
maven(url = uri("/absolute/path/to/synchro/dist/local-consumer/maven"))
mavenCentral()
}

Open a client from the application database directory. This suspend function verifies that the first pull contains the server-created note:

import android.content.Context
import com.trainstar.synchro.SynchroClient
import com.trainstar.synchro.SynchroConfig
suspend fun openNotesClient(
context: Context,
accessToken: String,
serverURL: String,
clientID: String,
databaseName: String,
): SynchroClient {
val noteID = "00000000-0000-4000-8000-000000000001"
val client = SynchroClient(
SynchroConfig(
dbPath = context.getDatabasePath(databaseName).absolutePath,
serverURL = serverURL,
authProvider = { accessToken },
clientID = clientID,
appVersion = "0.3.0",
),
context,
)
try {
client.start()
client.syncNow()
check(
client.query(
"SELECT id FROM notes WHERE id = ?",
arrayOf(noteID),
).firstOrNull()?.get("id") == noteID,
)
client.stop()
client.execute(
"UPDATE notes SET body = ? WHERE id = ?",
arrayOf("Edited offline", noteID),
)
client.start()
client.syncNow()
check(
client.query(
"SELECT body FROM notes WHERE id = ?",
arrayOf(noteID),
).firstOrNull()?.get("body") == "Edited offline",
)
return client
} catch (failure: Throwable) {
client.close()
throw failure
}
}

Get accessToken from the application identity provider. Call client.close() when the client is no longer needed. Pass http://10.0.2.2:8091 only for the local Android emulator tutorial adapter. Use an HTTPS URL in production.

The Kotlin client delivers onStatusChange, onConflict, onSyncEvent, and initialSyncCompleted callbacks synchronously from sync work. A callback must return before the application calls start, stop, retry, resetSchema, syncNow, or close. A synchronous call from a callback throws IllegalStateException before it cancels work, waits, or closes the database. To stop or close from a callback, launch the call on another coroutine and let the callback return.

Install the published package after Synchro 0.4.0-rc.4 is available:

Terminal window
npm install @trainstar/synchro-react-native@0.4.0-rc.4

For local development, install the staged package:

Terminal window
npm install /absolute/path/to/synchro/dist/local-consumer/npm/trainstar-synchro-react-native-0.4.0-rc.4.tgz

Pin the CLI toolchain and apply its delivered correction after each fresh npm installation:

Terminal window
npm install --save-dev --save-exact \
@react-native-community/cli@20.2.0 \
@react-native-community/cli-platform-android@20.2.0 \
@react-native-community/cli-platform-ios@20.2.0
node node_modules/@trainstar/synchro-react-native/tooling-patches/apply.cjs cli

Run this command before Codegen, bundle, or native build commands. The correction preserves literal source-directory names and temporary-directory prefixes while retaining the CLI’s authored cache suffix patterns. It verifies the installed source identities. See tooling correction requirements for provenance and trace-tooling setup.

Library projects that run bob build must also run this command after each fresh installation and before they build:

Terminal window
node node_modules/@trainstar/synchro-react-native/tooling-patches/apply.cjs bob

This uses Bob’s installed literal removal for its resolved outputs and TypeScript build-info paths. It rejects the project directory and paths outside the project before removal.

The React Native bridge requires both Synchro and GRDB. Use these exact Git dependencies for a published Synchro 0.4.0-rc.4 integration:

pod 'Synchro', :git => 'https://github.com/trainstar/synchro.git', :tag => 'v0.4.0-rc.4'
pod 'GRDB.swift', :git => 'https://github.com/groue/GRDB.swift.git', :tag => 'v7.8.0'

For a local pre-release integration, replace only Synchro with the staged path:

pod 'Synchro', :path => '/absolute/path/to/synchro/dist/local-consumer/apple/Synchro'
pod 'GRDB.swift', :git => 'https://github.com/groue/GRDB.swift.git', :tag => 'v7.8.0'

The bridge pod declares its Synchro dependency. The application Podfile still must provide the native Synchro and GRDB sources shown above.

After you add the Podfile declarations, install the pods:

Terminal window
cd ios
pod install

Use the React Native README for the complete JavaScript example. It uses the public initialize(), start(), syncNow(), queryOne(), execute(), and close() methods.

On Android, configure cleartext traffic only for the local emulator adapter. Use http://10.0.2.2:8091 for that development case. Production Android and iOS clients must use HTTPS.

Open a second client with a different stable client ID, such as quickstart-swift-b, quickstart-kotlin-b, or quickstart-rn-b. Authenticate as alice, use a distinct database file, call start() and syncNow(), then query the same note ID. Its body must be Edited offline.

The update is an ordinary local SQLite write. The client captures durable intent, seals and retries requests as needed, and applies the authoritative server result without an echo mutation. Do not edit local queue, cursor, checksum, version, or provenance tables.