Events and Recovery
TaskBridge models a backend task as a stream of TaskEvent objects. This is the core idea behind the Android SDK: commands create or mutate the task, while the event stream tells you what happened.
Event families
Known event types in the public API:
TaskStartedEventTaskProgressEventTaskMessageEventTaskSuspendedEventTaskActionAcceptedEventTaskCompletedEventTaskFailedEventTaskCancelledEvent
The SDK can also emit UnknownTaskEvent when a wire event type is not mapped yet.
That is an intentional compatibility boundary:
- your app can keep the stream alive when the backend adds a forward-compatible event;
- your code can log or ignore unknown events instead of crashing the whole observation pipeline.
Lifecycle in practice
stateDiagram-v2
[*] --> TASK_STARTED
TASK_STARTED --> TASK_PROGRESS
TASK_PROGRESS --> TASK_PROGRESS
TASK_PROGRESS --> TASK_MESSAGE
TASK_PROGRESS --> TASK_SUSPENDED
TASK_SUSPENDED --> TASK_ACTION_ACCEPTED
TASK_ACTION_ACCEPTED --> TASK_PROGRESS
TASK_PROGRESS --> TASK_COMPLETED
TASK_PROGRESS --> TASK_FAILED
TASK_PROGRESS --> TASK_CANCELLED
Terminal events are:
TASK_COMPLETEDTASK_FAILEDTASK_CANCELLED
When a terminal event arrives, the observation Flow finishes and the checkpoint for that task is cleared.
Real observation example
client.observeTaskEvents(taskId).collect { event ->
when (event) {
is TaskProgressEvent -> {
println("Progress: ${event.payload["progress"]}%")
}
is TaskCompletedEvent -> {
println("Task finished successfully!")
}
else -> {
println("Other event: $event")
}
}
}
This is the simplest consumer shape. In production, most apps should map these events into repository state or local storage rather than bind the raw stream directly to UI rendering.
Replay and deduplication
Transport delivery is at-least-once, not exactly-once. That means reconnects and transport failover may replay a suffix of already-seen events.
The SDK compensates for that by:
- persisting the last durable
eventIdin the checkpoint store; - deduplicating recently seen
eventIdvalues in memory; - resuming after the latest available watermark.
The precedence for resume is:
- explicit
lastEventIdpassed toobserveTaskEvents - stored checkpoint
- start from the retained stream beginning
Recovery flow
sequenceDiagram
autonumber
participant App as Android app
participant SDK as TaskBridge SDK
participant Store as Checkpoint store
participant API as Backend
App->>SDK: observeTaskEvents(taskId)
SDK->>Store: load checkpoint
Store-->>SDK: "1-0"
SDK->>API: subscribe with lastEventId=1-0
API-->>SDK: event 2-0
SDK->>Store: save 2-0
API--xSDK: network drop
SDK->>Store: load checkpoint
Store-->>SDK: "2-0"
SDK->>API: reconnect with lastEventId=2-0
API-->>SDK: replay newer events only
Suspensions and user actions
TaskSuspendedEvent means the backend task cannot continue without outside input.
The suspension payload includes:
suspendIdkindreasonCodeallowedActionsschemaVersionexpiresAtuiHintsinteraction
Typical app flow:
- observe
TaskSuspendedEvent - render the required UI
- build
TaskActionRequest - call
submitAction - wait for
TaskActionAcceptedEvent
Real action example:
client.submitAction(
context = Unit,
taskId = taskId,
action =
TaskActionRequest(
clientActionId = "act-xyz",
suspendId = suspendEvent.suspension.suspendId,
actionType = "approve",
payload = buildJsonObject { put("comment", "Looks good") },
),
)
The SDK also keeps an acknowledgement state for accepted actions so that a replayed suspension does not get emitted again as if it were still unresolved.
Progress and failure payloads
For structured payload parsing, the SDK provides helpers on the event model.
Examples:
TaskEvent.asProgress()TaskEvent.asFailure()
These are useful when your UI wants typed access to common payload shapes without hardcoding JSON extraction at every call site.
What not to assume
- Do not assume WebSocket delivery means no duplicates.
- Do not assume every task emits progress.
- Do not assume every suspension is a simple confirm/cancel dialog.
- Do not assume unknown events are fatal.
Recommended consumer boundary
For prototypes, collecting the stream in a ViewModel is acceptable.
For production, prefer:
- a repository or worker collects TaskBridge events;
- the repository persists or maps them into app state;
- the UI observes your own state model.
This keeps transport and replay complexity out of presentation code.