Get started with buoyient

Five steps to offline-first sync

Platform:
1
2
3
4
5

01 Add dependencies

Add buoyient to your module-level build.gradle.kts. Pick the modules you need.

Add the buoyient XCFramework to your Xcode project via Swift Package Manager.

build.gradle.kts
dependencies {
    implementation("com.elvdev.buoyient:syncable-objects:<version>")
}
build.gradle.kts
dependencies {
    implementation("com.elvdev.buoyient:syncable-objects:<version>")
    implementation("com.elvdev.buoyient:syncable-objects-hilt:<version>")
}
build.gradle.kts
dependencies {
    implementation("com.elvdev.buoyient:syncable-objects:<version>")
    implementation("com.elvdev.buoyient:syncable-objects-hilt:<version>")
    testImplementation("com.elvdev.buoyient:testing:<version>")
    debugImplementation("com.elvdev.buoyient:mock-mode:<version>") // for mock mode
}

That's it for dependencies. buoyient auto-initializes via androidx.startup — no Application.onCreate() code needed for initialization.

Step 1: Build the XCFramework

From the buoyient repo root, build the framework:

Terminal
./gradlew :syncable-objects:assembleBuoyientReleaseXCFramework

Step 2: Add to Xcode

Via SPM: In Xcode, go to File → Add Package Dependencies and point to the buoyient repo root (which contains Package.swift). Select the Buoyient library product.

Manual: Drag Buoyient.xcframework from syncable-objects/build/XCFrameworks/release/ into your Xcode project. Set embedding to Do Not Embed (static framework).

Swift-native APIs via SKIE

The framework includes SKIE, which automatically generates Swift-friendly wrappers:

  • suspend functions → Swift async/await
  • sealed class / sealed interface → Swift enum
  • Flow / StateFlow → Swift AsyncSequence

buoyient requires manual initialization on iOS — see Step 5 for SwiftUI App.init() setup and Info.plist configuration.

02 Define your data model

Describe your domain object below and watch the code generate in real time. Or skip ahead — you can always copy the template.

Describe your domain object below. The model, config, and service are written in Kotlin in a small shared module — SKIE then exposes them as native Swift APIs automatically.

Why Kotlin for Steps 2–4?

buoyient's service classes use Kotlin-specific features (KSerializer, @Serializable, coroutines) that can't be expressed in Swift. Even for iOS-only apps, you create a small Kotlin shared module for the service definitions. Your iOS app then calls them from Swift with async/await — see the Swift usage example in Step 4.

A typical structure looks like:

MyProject/
├── shared/              ← Kotlin module (model + service)
│   └── src/commonMain/kotlin/
│       ├── Todo.kt
│       ├── TodoRequestTag.kt
│       └── TodoService.kt
└── iosApp/              ← Swift app (calls the service)
    └── Sources/
        ├── TodoViewModel.swift
        └── ContentView.swift

Your model

Generated code

Todo.kt

03 Create a request tag

Each operation type gets a tag. This lets buoyient route responses and lets you customize handling per operation.

Each operation type gets a tag. This Kotlin enum goes in your shared module alongside the model.

TodoRequestTag.kt

04 Implement your service

Extend SyncableObjectService and expose domain-specific methods using the protected create(), update(), void(), and get() methods.

Define the Kotlin service in your shared module, then see the Swift usage below for how your iOS app calls it.

TodoService.kt

Key things to notice

  • CreateRequestBuilder is a lambda that builds the HTTP request — it receives the data, an idempotency key, and connectivity info
  • ResponseUnpacker extracts your object from the server response JSON
  • The serviceName must be unique — it's the SQLite partition key
  • Use HttpRequest.serverIdOrPlaceholder(serverId) in update/void URLs for offline safety

Your Swift code — how your iOS app uses the Kotlin service above

TodoViewModel.swift

How SKIE bridges Kotlin → Swift

  • Kotlin suspend fun → Swift async — call with try await
  • Kotlin sealed class → Swift enum — match with switch
  • Kotlin Flow<T> → Swift AsyncSequence — consume with for try await
  • The Kotlin service is defined once and shared across Android and iOS

05 Register for background sync

Tell buoyient about your service so it can sync in the background via WorkManager.

Register your service and configure iOS background task scheduling via BGTaskScheduler.

SyncModule.kt
MyApp.kt
MyApp.kt
MyApp.swift
AppDelegate.swift
Info.plist
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
    <string>com.elvdev.buoyient.sync</string>
</array>

iOS background sync

  • IosSyncScheduleNotifier.Companion.shared.registerHandler() must be called before the app finishes launching
  • The Info.plist entry is required for BGTaskScheduler to accept task submissions
  • iOS throttles background tasks (~1x/hour) — call Buoyient.shared.syncNow() on foreground transitions (scenePhase == .active) for reliable sync
  • Enable Background Modes capability with Background fetch and Background processing

Verify your setup

Run through this checklist to make sure everything is wired up correctly.

You're all set!

Your app now has offline-first sync. Time to build something great.

What's next?

Dive deeper into the full documentation for advanced patterns.

Creating a Service

ServerProcessingConfig, request builders, response unpacking, flow-based operations, and cross-service dependencies.

Read guide →

Integration Testing

TestServiceEnvironment, mock HTTP handlers, offline path testing, stateful mock server, and conflict simulation.

Read guide →

Mock Mode

Run your full app against fake data with a developer toggle. No real backend needed for manual testing.

Read guide →