Client Consumption
This guide starts after Server setup.
It uses that guide’s synchro_quickstart database, alice identity,
user:alice scope, and notes table.
Choose an artifact source
Section titled “Choose an artifact source”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:
make local-consumer-artifactsThis 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 |
Prerequisites
Section titled “Prerequisites”- 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.0and 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.0inclients/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.
Swift 6 callback upgrade
Section titled “Swift 6 callback upgrade”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 Foundationimport 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().
Android toolchain upgrade
Section titled “Android toolchain upgrade”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.
- Install JDK 17 and Android API 37.
- Set application
compileSdkandtargetSdkto 37. - Select AGP 9.4.1 and Kotlin compiler 2.4.21 in the root build configuration.
- Set the Gradle wrapper properties to these exact values:
distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.2-bin.zipdistributionSha256Sum=21bd5da2de49b642904077f38c0efdc7778e92b3ed0889392dca61b97e389607Native 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=falseandroid.newDsl=falseSet 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:
node node_modules/@trainstar/synchro-react-native/tooling-patches/apply.cjs rngpThe 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.
Kotlin
Section titled “Kotlin”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.Contextimport com.trainstar.synchro.SynchroClientimport 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.
React Native
Section titled “React Native”Install the published package after Synchro 0.4.0-rc.4 is available:
npm install @trainstar/synchro-react-native@0.4.0-rc.4For local development, install the staged package:
npm install /absolute/path/to/synchro/dist/local-consumer/npm/trainstar-synchro-react-native-0.4.0-rc.4.tgzPin the CLI toolchain and apply its delivered correction after each fresh npm installation:
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.0node node_modules/@trainstar/synchro-react-native/tooling-patches/apply.cjs cliRun 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:
node node_modules/@trainstar/synchro-react-native/tooling-patches/apply.cjs bobThis 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.
React Native iOS Podfile requirements
Section titled “React Native iOS Podfile requirements”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:
cd iospod installUse 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.
Confirm the second client
Section titled “Confirm the second client”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.