Service Task Management
API reference for service task operations in the Zync SDK.
Table of contents
- Getting Service Tasks
- Getting Service Task Details
- Updating Service Task Status
- Real-time Updates
- Error Handling
- Offline-First Behavior
- Best Practices
- Supported Modules
The ServiceTaskManager provides comprehensive offline-first service task management with real-time synchronization. Service tasks are individual work items within jobs that track specific activities, asset inspections, and form submissions with complete audit trails. The manager follows the offline-first approach where local data is returned immediately and background sync operations keep the data updated.
Getting Service Tasks
Retrieve a list of service tasks associated with a specific job or module using offline-first data access:
import zync.api.sync.models.ZuperModule
import zync.api.servicetask.models.GetServiceTasksResult
import zync.api.common.errors.ZyncError
val result = zync.serviceTasks.getServiceTasks(
module = ZuperModule.JOB,
moduleUid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
)
when (result) {
is GetServiceTasksResult.Success -> {
val tasks = result.serviceTasks
println("Found ${tasks.size} service tasks")
tasks.forEach { task ->
println("Task: ${task.serviceTaskTitle}")
println("Status: ${task.serviceTaskStatus}")
println("Sequence: ${task.sequenceNo}")
println("Description: ${task.serviceTaskDescription ?: "No description"}")
// Check assigned users
if (task.assignedTo.isNotEmpty()) {
println("Assigned to:")
task.assignedTo.forEach { assignment ->
println(" - ${assignment.user.displayName} (${assignment.team.teamName})")
}
}
// Check asset information
task.serviceTaskAsset?.let { asset ->
println("Asset: ${asset.assetName} (${asset.assetCode})")
println("Asset Status: ${asset.assetStatus}")
}
// Check inspection form
task.inspectionForm?.let { form ->
println("Inspection Form: ${form.assetFormName}")
println("Form Fields: ${form.inspectionFormFields.size}")
}
println("---")
}
}
is GetServiceTasksResult.Failure -> {
println("Error: ${result.error.message}")
when (result.error) {
is ZyncError.Network -> {
println("Check your internet connection")
}
is ZyncError.Error -> {
result.error.code?.let { code ->
println("Error code: $code")
}
}
}
}
}
let result = try await zync.serviceTasks.getServiceTasks(
module: .job,
moduleUid: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
)
switch onEnum(of: result) {
case .success(let success):
let tasks = success.serviceTasks
print("Found \(tasks.count) service tasks")
for task in tasks {
print("Task: \(task.serviceTaskTitle)")
print("Status: \(task.serviceTaskStatus)")
print("Sequence: \(task.sequenceNo)")
print("Description: \(task.serviceTaskDescription ?? "No description")")
// Check assigned users
if !task.assignedTo.isEmpty {
print("Assigned to:")
for assignment in task.assignedTo {
print(" - \(assignment.user.displayName ?? "") (\(assignment.team.teamName ?? ""))")
}
}
// Check asset information
if let asset = task.serviceTaskAsset {
print("Asset: \(asset.assetName) (\(asset.assetCode))")
print("Asset Status: \(asset.assetStatus?.rawValue ?? "unknown")")
}
// Check inspection form
if let form = task.inspectionForm {
print("Inspection Form: \(form.assetFormName)")
print("Form Fields: \(form.inspectionFormFields.count)")
}
print("---")
}
case .failure(let failure):
print("Error: \(failure.error.message)")
switch onEnum(of: failure.error) {
case .network:
print("Check your internet connection")
case .error(let error):
print("Error: \(error.message)")
if let code = error.code {
print("Error code: \(code)")
}
}
}
Getting Service Task Details
Retrieve detailed information for a specific service task, including status history, assignments, and related data:
import zync.api.servicetask.models.GetServiceTaskDetailResult
import zync.api.common.errors.ZyncError
val result = zync.serviceTasks.getServiceTaskDetail("9e4f2c8d-1a3b-4c5d-8e9f-2a3b4c5d6e7f")
when (result) {
is GetServiceTaskDetailResult.Success -> {
val taskDetail = result.serviceTaskDetail
println("Task: ${taskDetail.serviceTaskTitle}")
println("Status: ${taskDetail.serviceTaskStatus}")
println("Module: ${taskDetail.module}")
println("Description: ${taskDetail.serviceTaskDescription ?: "No description"}")
// Display timing information
taskDetail.estimateDuration?.let { estimate ->
println("Estimated Duration: ${estimate.days}d ${estimate.hours}h ${estimate.minutes}m")
}
taskDetail.scheduledDuration?.let { scheduled ->
println("Scheduled Duration: ${scheduled} hours")
}
taskDetail.actualDuration?.let { actual ->
println("Actual Duration: ${actual} hours")
}
// Display timing details
println("Start Time: ${taskDetail.actualStartTime ?: "Not started"}")
println("End Time: ${taskDetail.actualEndTime ?: "Not completed"}")
// Display status history
if (taskDetail.serviceTaskStatusHistory.isNotEmpty()) {
println("Status History:")
taskDetail.serviceTaskStatusHistory.forEach { history ->
println(" - ${history.status} at ${history.createdAt}")
println(" By: ${history.doneBy.displayName}")
history.remarks?.let { remarks ->
println(" Remarks: $remarks")
}
}
}
// Display assigned users
taskDetail.assignedTo?.let { assignments ->
if (assignments.isNotEmpty()) {
println("Assigned Users:")
assignments.forEach { assignment ->
println(" - ${assignment.user.displayName} (${assignment.team.teamName})")
}
}
}
// Display asset information
taskDetail.serviceTaskAsset?.let { asset ->
println("Asset Details:")
println(" Name: ${asset.assetName}")
println(" Code: ${asset.assetCode}")
println(" Serial: ${asset.assetSerialNumber ?: "N/A"}")
println(" Quantity: ${asset.assetQuantity}")
println(" Status: ${asset.assetStatus}")
asset.warrantyExpiryDate?.let { warranty ->
println(" Warranty Expires: $warranty")
}
}
// Display inspection form information
taskDetail.inspectionForm?.let { form ->
println("Inspection Form:")
println(" Name: ${form.assetFormName}")
println(" Description: ${form.description ?: "No description"}")
println(" Active: ${form.isActive}")
println(" Fields: ${form.inspectionFormFields.size}")
// Show form submission info
taskDetail.inspectionFormSubmissionUid?.let { submissionUid ->
println(" Submission UID: $submissionUid")
}
taskDetail.latestInspectionFormSubmissionTimestamp?.let { timestamp ->
println(" Latest Submission: $timestamp")
}
}
}
is GetServiceTaskDetailResult.Failure -> {
println("Error: ${result.error.message}")
when (result.error) {
is ZyncError.Network -> {
println("Check your internet connection")
}
is ZyncError.Error -> {
result.error.code?.let { code ->
println("Error code: $code")
}
}
}
}
}
let result = try await zync.serviceTasks.getServiceTaskDetail(serviceTaskUid: "9e4f2c8d-1a3b-4c5d-8e9f-2a3b4c5d6e7f")
switch onEnum(of: result) {
case .success(let success):
let taskDetail = success.serviceTaskDetail
print("Task: \(taskDetail.serviceTaskTitle)")
print("Status: \(taskDetail.serviceTaskStatus)")
print("Module: \(taskDetail.module)")
print("Description: \(taskDetail.serviceTaskDescription ?? "No description")")
// Display timing information
if let estimate = taskDetail.estimateDuration {
print("Estimated Duration: \(estimate.days)d \(estimate.hours)h \(estimate.minutes)m")
}
if let scheduled = taskDetail.scheduledDuration {
print("Scheduled Duration: \(scheduled) hours")
}
if let actual = taskDetail.actualDuration {
print("Actual Duration: \(actual) hours")
}
// Display timing details
print("Start Time: \(taskDetail.actualStartTime ?? "Not started")")
print("End Time: \(taskDetail.actualEndTime ?? "Not completed")")
// Display status history
if !taskDetail.serviceTaskStatusHistory.isEmpty {
print("Status History:")
for history in taskDetail.serviceTaskStatusHistory {
print(" - \(history.status) at \(history.createdAt ?? "")")
print(" By: \(history.doneBy.displayName ?? "")")
if let remarks = history.remarks {
print(" Remarks: \(remarks)")
}
}
}
// Display assigned users
if let assignments = taskDetail.assignedTo, !assignments.isEmpty {
print("Assigned Users:")
for assignment in assignments {
print(" - \(assignment.user.displayName ?? "") (\(assignment.team.teamName ?? ""))")
}
}
// Display asset information
if let asset = taskDetail.serviceTaskAsset {
print("Asset Details:")
print(" Name: \(asset.assetName)")
print(" Code: \(asset.assetCode)")
print(" Serial: \(asset.assetSerialNumber ?? "N/A")")
print(" Quantity: \(asset.assetQuantity)")
print(" Status: \(asset.assetStatus?.rawValue ?? "unknown")")
if let warranty = asset.warrantyExpiryDate {
print(" Warranty Expires: \(warranty)")
}
}
// Display inspection form information
if let form = taskDetail.inspectionForm {
print("Inspection Form:")
print(" Name: \(form.assetFormName)")
print(" Description: \(form.description ?? "No description")")
print(" Active: \(form.isActive)")
print(" Fields: \(form.inspectionFormFields.count)")
// Show form submission info
if let submissionUid = taskDetail.inspectionFormSubmissionUid {
print(" Submission UID: \(submissionUid)")
}
if let timestamp = taskDetail.latestInspectionFormSubmissionTimestamp {
print(" Latest Submission: \(timestamp)")
}
}
case .failure(let failure):
print("Error: \(failure.error.message)")
switch onEnum(of: failure.error) {
case .network:
print("Check your internet connection")
case .error(let error):
print("Error: \(error.message)")
if let code = error.code {
print("Error code: \(code)")
}
}
}
Updating Service Task Status
Update service task status with optional user assignment support. This method provides complete offline-first service task status management with assignment capabilities:
import zync.api.servicetask.models.ZyncServiceTaskStatusUpdateData
import zync.api.servicetask.models.ZyncServiceTaskStatus
import zync.api.servicetask.models.UpdateServiceTaskStatusResult
import zync.api.common.errors.ZyncError
// Simple status update
val statusUpdate = ZyncServiceTaskStatusUpdateData(
jobUid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
serviceTaskUid = "9e4f2c8d-1a3b-4c5d-8e9f-2a3b4c5d6e7f",
serviceTaskStatus = ZyncServiceTaskStatus.IN_PROGRESS,
remarks = "Started working on the equipment inspection"
)
// Status update with assignment
val statusUpdateWithAssignment = ZyncServiceTaskStatusUpdateData(
jobUid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
serviceTaskUid = "9e4f2c8d-1a3b-4c5d-8e9f-2a3b4c5d6e7f",
serviceTaskStatus = ZyncServiceTaskStatus.COMPLETED,
remarks = "Equipment inspection completed successfully",
assignToTask = true, // Assign current user to this task
assignToJob = true, // Also assign current user to the job
assignFromTeam = "team-uid-123" // Assign from specific team
)
val result = zync.serviceTasks.updateServiceTaskStatus(statusUpdateWithAssignment)
when (result) {
is UpdateServiceTaskStatusResult.Success -> {
println("Service task status updated successfully")
// Status is immediately available locally and will sync to server
}
is UpdateServiceTaskStatusResult.Failure -> {
println("Failed to update status: ${result.error.message}")
when (result.error) {
is ZyncError.Network -> {
println("Update saved locally, will sync when online")
}
is ZyncError.Error -> {
result.error.code?.let { code ->
println("Error code: $code")
}
}
}
}
}
// Simple status update
let statusUpdate = ZyncServiceTaskStatusUpdateData(
jobUid: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
serviceTaskUid: "9e4f2c8d-1a3b-4c5d-8e9f-2a3b4c5d6e7f",
serviceTaskStatus: .inProgress,
remarks: "Started working on the equipment inspection",
assignToTask: false,
assignToJob: false,
assignFromTeam: nil
)
// Status update with assignment
let statusUpdateWithAssignment = ZyncServiceTaskStatusUpdateData(
jobUid: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
serviceTaskUid: "9e4f2c8d-1a3b-4c5d-8e9f-2a3b4c5d6e7f",
serviceTaskStatus: .completed,
remarks: "Equipment inspection completed successfully",
assignToTask: true, // Assign current user to this task
assignToJob: true, // Also assign current user to the job
assignFromTeam: "team-uid-123" // Assign from specific team
)
let result = try await zync.serviceTasks.updateServiceTaskStatus(serviceTaskStatusUpdateData: statusUpdateWithAssignment)
switch onEnum(of: result) {
case .success:
print("Service task status updated successfully")
// Status is immediately available locally and will sync to server
case .failure(let failure):
print("Failed to update status: \(failure.error.message)")
switch onEnum(of: failure.error) {
case .network:
print("Update saved locally, will sync when online")
case .error(let error):
print("Error: \(error.message)")
if let code = error.code {
print("Error code: \(code)")
}
}
}
Service Task Status Types
The available service task statuses are:
- OPEN: Task is created and ready to be worked on
- IN_PROGRESS: Task is currently being worked on
- ON_HOLD: Task is temporarily paused
- COMPLETED: Task has been finished successfully
- CANCELED: Task has been canceled
- INCOMPLETE: Task was not completed properly
Assignment Logic
The status update supports flexible assignment options:
- assignToTask: Assigns the current user to this specific service task
- assignToJob: Assigns the current user to the parent job
- assignFromTeam: Specifies which team the assignment should be from (optional)
Real-time Updates
Monitor service task changes in real-time using the reactive Flow API:
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.collect
import zync.api.servicetask.models.ServiceTaskChangeEvent
import zync.api.sync.models.ZuperModule
// Collect service task changes with real-time updates
val job = launch {
zync.serviceTasks.observeServiceTaskChanges(
module = ZuperModule.JOB,
moduleUid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
).collect { changeEvent ->
when (changeEvent) {
is ServiceTaskChangeEvent.ServiceTasksUpdated -> {
val updatedTasks = changeEvent.updatedServiceTasks
val deletedTaskUids = changeEvent.deletedServiceTaskUids
// Handle deletions first - remove from current list
deletedTaskUids.forEach { deletedUid ->
currentTaskList.removeAll { it.serviceTaskUid == deletedUid }
println("Removed deleted task: $deletedUid")
}
// Handle updates and additions
updatedTasks.forEach { updatedTask ->
val existingIndex = currentTaskList.indexOfFirst {
it.serviceTaskUid == updatedTask.serviceTaskUid
}
if (existingIndex != -1) {
// Update existing task
currentTaskList[existingIndex] = updatedTask
println("Updated existing task: ${updatedTask.serviceTaskTitle}")
} else {
// Add new task
currentTaskList.add(updatedTask)
println("Added new task: ${updatedTask.serviceTaskTitle}")
}
}
// Show notification for changes
val totalChanges = updatedTasks.size + deletedTaskUids.size
if (totalChanges > 0) {
showUpdateAvailableChip("$totalChanges service task changes available")
}
// Refresh UI with updated list
notifyTaskListChanged()
}
is ServiceTaskChangeEvent.ServiceTaskUploadStatusChanged -> {
val taskUid = changeEvent.serviceTaskUid
val uploadStatus = changeEvent.uploadStatus
// Find existing task in your list
val existingTask = currentTaskList.find { it.serviceTaskUid == taskUid }
if (existingTask != null) {
println("Upload status changed for ${existingTask.serviceTaskTitle}: $uploadStatus")
// Update UI to show upload progress or status
notifyTaskItemChanged(taskUid)
}
}
}
}
}
// Cancel when done
job.cancel()
// Observe service task changes using SKIE's AsyncSequence support
let task = Task {
for await changeEvent in zync.serviceTasks.observeServiceTaskChanges(
module: .job,
moduleUid: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
) {
switch changeEvent {
case .serviceTasksUpdated(let updateEvent):
let updatedTasks = updateEvent.updatedServiceTasks
let deletedTaskUids = updateEvent.deletedServiceTaskUids
// Handle deletions first - remove from current list
for deletedUid in deletedTaskUids {
currentTaskList.removeAll { $0.serviceTaskUid == deletedUid }
print("Removed deleted task: \(deletedUid)")
}
// Handle updates and additions
for updatedTask in updatedTasks {
if let existingIndex = currentTaskList.firstIndex(where: {
$0.serviceTaskUid == updatedTask.serviceTaskUid
}) {
// Update existing task
currentTaskList[existingIndex] = updatedTask
print("Updated existing task: \(updatedTask.serviceTaskTitle)")
} else {
// Add new task
currentTaskList.append(updatedTask)
print("Added new task: \(updatedTask.serviceTaskTitle)")
}
}
// Show notification for changes
let totalChanges = updatedTasks.count + deletedTaskUids.count
if totalChanges > 0 {
await showUpdateAvailableChip("\(totalChanges) service task changes available")
}
// Refresh UI with updated list
await notifyTaskListChanged()
case .serviceTaskUploadStatusChanged(let statusEvent):
let taskUid = statusEvent.serviceTaskUid
let uploadStatus = statusEvent.uploadStatus
// Find existing task in your list
if let existingTask = currentTaskList.first(where: { $0.serviceTaskUid == taskUid }) {
print("Upload status changed for \(existingTask.serviceTaskTitle): \(uploadStatus)")
// Update UI to show upload progress or status
notifyTaskItemChanged(taskUid)
}
}
}
}
// Cancel when done
task.cancel()
Error Handling
Common Error Types
when (result) {
is GetServiceTasksResult.Success -> {
// Handle success
val tasks = result.serviceTasks
displayTasks(tasks)
}
is GetServiceTasksResult.Failure -> {
when (result.error) {
is ZyncError.Network -> {
println("Network error - showing cached data")
// Show offline indicator but continue with cached data
showOfflineIndicator()
loadCachedTasks()
}
is ZyncError.Error -> {
println("API error: ${result.error.message}")
result.error.code?.let { code ->
when (code) {
401 -> showLoginPrompt()
403 -> showAccessDeniedMessage()
404 -> showNoTasksMessage()
else -> showGenericErrorMessage()
}
}
}
}
}
}
switch onEnum(of: result) {
case .success(let success):
// Handle success
let tasks = success.serviceTasks
displayTasks(tasks)
case .failure(let failure):
switch onEnum(of: failure.error) {
case .network:
print("Network error - showing cached data")
// Show offline indicator but continue with cached data
showOfflineIndicator()
loadCachedTasks()
case .error(let error):
print("API error: \(error.message)")
if let code = error.code {
switch code {
case 401:
showLoginPrompt()
case 403:
showAccessDeniedMessage()
case 404:
showNoTasksMessage()
default:
showGenericErrorMessage()
}
}
}
}
Offline-First Behavior
The Zync SDK implements a sophisticated offline-first pattern for service tasks, ensuring optimal performance and user experience regardless of network conditions.
Initial Sync vs Cached Data
First Time Access:
- Performs synchronous initial sync from server
- Creates sync metadata to track sync state
- Returns complete data set after successful sync
- Marks initial sync as complete for future optimizations
Subsequent Access:
- Returns cached data immediately (no waiting)
- Triggers background delta sync for updated data
- Updates cache silently without blocking user interface
- Uses last sync timestamp to fetch only changed tasks
Background Delta Sync
// First call - performs initial sync
val initialResult = zync.serviceTasks.getServiceTasks(
module = ZuperModule.JOB,
moduleUid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
)
// User waits for sync to complete
// Subsequent calls - immediate response + background sync
val cachedResult = zync.serviceTasks.getServiceTasks(
module = ZuperModule.JOB,
moduleUid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
)
// User gets immediate response with cached data
// SDK performs background delta sync for any updates since last sync
// First call - performs initial sync
let initialResult = try await zync.serviceTasks.getServiceTasks(
module: .job,
moduleUid: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
)
// User waits for sync to complete
// Subsequent calls - immediate response + background sync
let cachedResult = try await zync.serviceTasks.getServiceTasks(
module: .job,
moduleUid: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
)
// User gets immediate response with cached data
// SDK performs background delta sync for any updates since last sync
Network Resilience
Offline Operations:
- All status updates work offline and queue for sync
- Local database serves as single source of truth
- Changes are preserved and synced when connectivity returns
Online Operations:
- Background sync runs automatically without blocking UI
- Smart conflict resolution for concurrent modifications
- Automatic retry with exponential backoff for failed operations
Performance Benefit: After initial sync, users get instant responses while the SDK keeps data fresh in the background. This provides the best of both worlds - immediate responsiveness and up-to-date data.
Best Practices
Optimal Usage Patterns
Efficient Task Loading
class ServiceTaskViewModel {
private var cachedTasks: List<ZyncServiceTask> = emptyList()
private var isInitialLoad = true
suspend fun loadServiceTasks() {
val result = zync.serviceTasks.getServiceTasks(
module = ZuperModule.JOB,
moduleUid = jobUid
)
when (result) {
is GetServiceTasksResult.Success -> {
cachedTasks = result.serviceTasks
// First load takes longer (initial sync)
// Subsequent loads are instant (cached + background sync)
isInitialLoad = false
updateUI(result.serviceTasks)
}
is GetServiceTasksResult.Failure -> {
handleError(result.error)
}
}
}
// Use real-time updates for better UX
suspend fun observeTaskChanges() {
zync.serviceTasks.observeServiceTaskChanges(ZuperModule.JOB, jobUid)
.collect { event ->
updateUI(event)
}
}
}
Offline-First Operations
// Update task status offline - it'll sync automatically
suspend fun completeTask(taskUid: String, remarks: String) {
val statusUpdate = ZyncServiceTaskStatusUpdateData(
jobUid = currentJobUid,
serviceTaskUid = taskUid,
serviceTaskStatus = ZyncServiceTaskStatus.COMPLETED,
remarks = remarks,
assignToTask = true, // Take ownership
assignToJob = false
)
val result = zync.serviceTasks.updateServiceTaskStatus(statusUpdate)
// Status is immediately available locally
// Will sync to server when connectivity is available
}
Performance Guidelines
Do’s ✅
- Let the SDK manage sync timing automatically
- Use cached data for immediate UI responsiveness
- Update task status optimistically (offline-first)
- Use real-time updates for keeping UI fresh
- Embrace background sync for keeping data current
Don’ts ❌
- Don’t force manual sync unless absolutely necessary
- Don’t ignore offline capabilities - embrace offline-first patterns
- Don’t block UI while waiting for sync operations
Error Handling Best Practices
suspend fun handleServiceTaskOperations() {
try {
val result = zync.serviceTasks.getServiceTasks(module, moduleUid)
when (result) {
is GetServiceTasksResult.Success -> {
// Handle success
displayTasks(result.serviceTasks)
}
is GetServiceTasksResult.Failure -> {
when (result.error) {
is ZyncError.Network -> {
// Show offline indicator but continue with cached data
showOfflineIndicator()
loadCachedData()
}
is ZyncError.Error -> {
// Log error and show user-friendly message
logError(result.error)
showErrorMessage("Unable to sync tasks. Using cached data.")
loadCachedData()
}
}
}
}
} catch (exception: Exception) {
// Handle unexpected errors gracefully
logError(exception)
showGenericErrorMessage()
}
}
Security Considerations
- Offline Security: Service tasks are stored securely in encrypted local database
- Sync Security: All network operations use secure connections
- Data Privacy: Assignment and status data are handled according to privacy settings
Key Takeaway: Embrace the offline-first pattern. The SDK is designed to work seamlessly offline with automatic sync - don’t fight this design by forcing online operations.
Supported Modules
Currently, the ServiceTaskManager supports:
- ZuperModule.JOB: Job-related service tasks
Note: More modules will be supported in future releases.