Skip to content

Kotlin: Subscribe to events

on registers a handler for the events the engine raises as writes are applied and runs settle, and returns the function that removes it. It is the signal a native app refreshes a screen from, because there is no live query on this client.

Examples#

Basic#

// app/src/main/kotlin/com/example/todo/TodoScreen.kt (excerpt)
val stop = kizunasync.on { event ->
    events.tryEmit(event)
}

Unsubscribe with the screen#

// app/src/main/kotlin/com/example/todo/TodoScreen.kt (excerpt)
import androidx.compose.runtime.DisposableEffect
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.launch

DisposableEffect(Unit) {
    val job = scope.launch {
        val stop = kizunasync.on { event -> events.tryEmit(event) }
        try {
            awaitCancellation()
        } finally {
            stop()
        }
    }
    onDispose { job.cancel() }
}

Parameters#

NameTypeRequiredDescription
handler(KizunaSyncEngineEvent) -> UnitYesCalled once per event, in emission order, on the delivery thread the client owns. It may call back into the client.

The events it receives#

NameTypeRequiredDescription
LocalChanged——Raised after every local write, after a pull boundary commits rows or replays the outbox over them, after a rejected verdict reverts a row, after a dead letter reverts optimistic rows, and after Reset.
QueueDepthdepth: UInt—Raised by each local write with the depth the next run will drain, the same number Outbox depth reports. The Host scheduler wakes a run on a depth above zero.
MutationRejectedmutationId: String, reason: String—Raised once per refused write, after the revert and the journal entry land.
BatchAbortedoffenderMutationId: String, reason: String—Raised once for an atomic batch the server refused, naming the member that caused it.
DeadLettermutationId: String, reason: String—Raised once per write the retry budget dropped. reason is PERMANENT_TRANSPORT after five consecutive failures, except a batch-too-large refusal (KZP02) of an atomic batch, which is dropped at once with the server's own message as reason.
ColumnOverwrittentable: String, pk: String, column: String, loserValueJson: String, winnerMutationId: String, conflictMode: String—Raised for each column-level conflict a pull reported, carrying the value that lost as JSON.
CheckpointExpired——Raised when the server invalidated the pull token. The next pull restarts the keyset and the closing boundary replaces the local snapshot.
ResetRequiredreason: String?—Raised when pull or push returned the reset signal, with reason reset_required, or when a token named another user than the owner of the local database, with reason identity_changed, as Set access token describes. The client soft-blocks until Reset runs.

Returns#

() -> Unit, the unsubscribe function. It is safe to call from any thread, including main, because it hands the removal to the client's own coroutine scope.

Errors#

CodeCondition
ENGINE_UNAVAILABLEThe client has no engine because Initializing has not run.

Notes#

Events arrive on one delivery thread that the client owns, one at a time and in the order the engine emitted them, never on the thread that made the call. A handler may call the client back, because the engine answers on its own thread while the delivery thread waits for the answer. A handler that throws does not stop the events after it. The client catches the throwable and logs it at WARNING on the java.util.logging logger com.kizunasync.kizunasync, which Android forwards to logcat.

Attachment statuses from Watch an attachment share that thread, so a handler that runs long delays every event and status queued behind it. Hand longer work to a Flow, a channel, or another dispatcher, then return. The examples above do that.

Every handler stays registered until its function is called, so a screen that subscribes when it appears unsubscribes when it goes away. Offline writes shows the pattern in an app, and Fetch data is what a handler re-runs to get the new rows.

Found a problem? Edit this page on GitHub