Note Management

Comprehensive guide to using the Zync SDK’s note management features.

Table of contents

  1. Add Note
  2. Update Note
  3. Get Note Detail
  4. Get Notes
  5. Delete Note
    1. Smart Delete Behavior
    2. Delete Examples
    3. Delete Logic Flow
  6. Refresh Note Data
    1. Mark Note as Stale
  7. Offline-First Behavior
    1. Initial Sync vs Cached Data
    2. Background Delta Sync
    3. Sync State Management
    4. Network Resilience
  8. Performance and Sync Optimizations
    1. Bulk Loading Optimizations
    2. CRUD Operation Smart Merging
    3. Sync Performance Features
    4. Smart Relationship Handling
    5. Performance Monitoring
  9. Best Practices
    1. When to Use Each Method
    2. Optimal Usage Patterns
      1. Efficient Note Loading
      2. Offline-First Operations
    3. Performance Guidelines
      1. Do’s ✅
      2. Don’ts ❌
    4. Error Handling Best Practices
    5. Memory and Resource Management
    6. Security Considerations

The ZyncNotesManager provides comprehensive offline-first note management with advanced sync capabilities. It features immediate local storage with background synchronization, smart conflict resolution for offline edits, automatic retry mechanisms, and real-time data access with cached responses. All operations follow the offline-first pattern where local data serves as the primary source of truth, with seamless background synchronization when online.

Add Note

import zync.public.notes.models.ZyncAddNoteContent
import zync.public.notes.models.ZyncNoteVisibilityType
import zync.public.notes.models.ZyncNotePrimaryModule
import zync.public.notes.models.ZyncNoteAssociatedJob
import zync.public.notes.models.ZyncNoteAttachment
import zync.public.notes.models.AddNoteResult
import zync.public.notes.models.UpdateNoteResult
import zync.public.notes.models.GetNoteResult
import zync.public.notes.models.NotesResult
import zync.public.notes.models.DeleteNoteResult
import zync.public.notes.models.ZyncUpdateNoteContent
import zync.api.common.errors.ZyncError

val noteContent = ZyncAddNoteContent(
    note = "Equipment inspection completed successfully",
    noteType = "TEXT",
    canNotifyOthers = true,
    noteVisibilityType = ZyncNoteVisibilityType.PUBLIC,
    attachments = listOf(
        ZyncNoteAttachment(
            attachmentUid = "f47ac10b-58cc-4372-a567-0e02b2c3d479",
            attachment = "file:///path/to/inspection_photo.jpg",
            attachmentName = "inspection_photo.jpg",
            attachmentType = "IMAGE",
            attachmentSize = 2048576L,
            attachmentDescription = "Equipment inspection photo",
            tags = listOf("inspection", "equipment"),
            thumbnailUrl = null
        )
    ),
    userMentions = listOf("user-123-uid", "user-456-uid"),
    associatedJob = ZyncNoteAssociatedJob(
        jobUid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        jobNumber = "JOB-2024-001"
    ),
    associatedCustomer = null,
    associatedOrganization = null,
    associatedProperty = null,
    associatedAsset = null,
    associatedQuote = null,
    associatedInvoice = null,
    associatedContract = null,
    associatedRequest = null,
    associatedProject = null,
    primaryModule = ZyncNotePrimaryModule(
        module = "job",
        moduleUid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
    ),
    isPinned = false,
    geoCoordinates = listOf(37.7749, -122.4194) // Optional coordinates
)

val result = zync.notes.addNote(content = noteContent)

when (result) {
    is AddNoteResult.Success -> {
        val note = result.note
        println("Note created: ${note.noteUid}")
        println("Content: ${note.note}")
        println("Created by: ${note.createdBy?.displayName}")
    }
    is AddNoteResult.Failure -> {
        println("Failed to create note: ${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 noteContent = ZyncAddNoteContent(
    note: "Equipment inspection completed successfully",
    noteType: "TEXT",
    canNotifyOthers: true,
    noteVisibilityType: .public,
    attachments: [
        ZyncNoteAttachment(
            attachmentUid: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
            attachment: "file:///path/to/inspection_photo.jpg",
            attachmentName: "inspection_photo.jpg",
            attachmentType: "IMAGE",
            attachmentSize: 2048576,
            attachmentDescription: "Equipment inspection photo",
            tags: ["inspection", "equipment"],
            thumbnailUrl: nil
        )
    ],
    userMentions: ["user-123-uid", "user-456-uid"],
    associatedJob: ZyncNoteAssociatedJob(
        jobUid: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        jobNumber: "JOB-2024-001"
    ),
    associatedCustomer: nil,
    associatedOrganization: nil,
    associatedProperty: nil,
    associatedAsset: nil,
    associatedQuote: nil,
    associatedInvoice: nil,
    associatedContract: nil,
    associatedRequest: nil,
    associatedProject: nil,
    primaryModule: ZyncNotePrimaryModule(
        module: "job",
        moduleUid: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
    ),
    isPinned: false,
    geoCoordinates: [37.7749, -122.4194] // Optional coordinates
)

let result = try await zync.notes.addNote(content: noteContent)

switch onEnum(of: result) {
case .success(let success):
    let note = success.note
    print("Note created: \(note.noteUid)")
    print("Content: \(note.note ?? "")")
    print("Created by: \(note.createdBy?.displayName ?? "Unknown")")
case .failure(let failure):
    print("Failed to create note: \(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)")
        }
    }
}

Update Note

import zync.public.notes.models.ZyncUpdateNoteContent
import zync.public.notes.models.ZyncNoteAttachment
import zync.public.notes.models.ZyncNoteVisibilityType
import zync.public.notes.models.ZyncNoteAssociatedJob
import zync.public.notes.models.UpdateNoteResult
import zync.api.common.errors.ZyncError

val updateContent = ZyncUpdateNoteContent(
    noteUid = "9e4f2c8d-1a3b-4c5d-8e9f-2a3b4c5d6e7f",
    note = "Updated: Equipment inspection completed with minor issues found",
    noteType = "TEXT",
    noteVisibilityType = ZyncNoteVisibilityType.PUBLIC,
    attachments = listOf(
        ZyncNoteAttachment(
            attachmentUid = "f47ac10b-58cc-4372-a567-0e02b2c3d479",
            attachment = "file:///path/to/inspection_photo.jpg",
            attachmentName = "inspection_photo.jpg",
            attachmentType = "IMAGE",
            attachmentSize = 2048576L,
            attachmentDescription = "Equipment inspection photo",
            tags = listOf("inspection", "equipment"),
            thumbnailUrl = null
        ),
        ZyncNoteAttachment(
            attachmentUid = "a12b3c4d-5e6f-7890-abcd-ef1234567890",
            attachment = "file:///path/to/issue_report.pdf",
            attachmentName = "issue_report.pdf",
            attachmentType = "DOCUMENT",
            attachmentSize = 1048576L,
            attachmentDescription = "Issue report document",
            tags = listOf("report", "issue"),
            thumbnailUrl = null
        )
    ),
    userMentions = listOf("user-123-uid", "supervisor-uid"),
    associatedJob = ZyncNoteAssociatedJob(
        jobUid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        jobNumber = "JOB-2024-001"
    ),
    associatedCustomer = null,
    associatedOrganization = null,
    associatedProperty = null,
    associatedAsset = null,
    associatedQuote = null,
    associatedInvoice = null,
    associatedContract = null,
    associatedRequest = null,
    associatedProject = null,
    geoCoordinates = listOf(37.7749, -122.4194),
    isV2Note = true,
    isPinned = true
)

val result = zync.notes.updateNote(content = updateContent)

when (result) {
    is UpdateNoteResult.Success -> {
        val updatedNote = result.note
        println("Note updated: ${updatedNote.noteUid}")
        println("New content: ${updatedNote.note}")
        println("Is pinned: ${updatedNote.isPinned}")
    }
    is UpdateNoteResult.Failure -> {
        println("Failed to update note: ${result.error.message}")
    }
}
let updateContent = ZyncUpdateNoteContent(
    noteUid: "9e4f2c8d-1a3b-4c5d-8e9f-2a3b4c5d6e7f",
    note: "Updated: Equipment inspection completed with minor issues found",
    noteType: "TEXT",
    noteVisibilityType: .public,
    attachments: [
        ZyncNoteAttachment(
            attachmentUid: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
            attachment: "file:///path/to/inspection_photo.jpg",
            attachmentName: "inspection_photo.jpg",
            attachmentType: "IMAGE",
            attachmentSize: 2048576,
            attachmentDescription: "Equipment inspection photo",
            tags: ["inspection", "equipment"],
            thumbnailUrl: nil
        ),
        ZyncNoteAttachment(
            attachmentUid: "a12b3c4d-5e6f-7890-abcd-ef1234567890",
            attachment: "file:///path/to/issue_report.pdf",
            attachmentName: "issue_report.pdf",
            attachmentType: "DOCUMENT",
            attachmentSize: 1048576,
            attachmentDescription: "Issue report document",
            tags: ["report", "issue"],
            thumbnailUrl: nil
        )
    ],
    userMentions: ["user-123-uid", "supervisor-uid"],
    associatedJob: ZyncNoteAssociatedJob(
        jobUid: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        jobNumber: "JOB-2024-001"
    ),
    associatedCustomer: nil,
    associatedOrganization: nil,
    associatedProperty: nil,
    associatedAsset: nil,
    associatedQuote: nil,
    associatedInvoice: nil,
    associatedContract: nil,
    associatedRequest: nil,
    associatedProject: nil,
    geoCoordinates: [37.7749, -122.4194],
    isV2Note: true,
    isPinned: true
)

let result = try await zync.notes.updateNote(content: updateContent)

switch onEnum(of: result) {
case .success(let success):
    let updatedNote = success.note
    print("Note updated: \(updatedNote.noteUid)")
    print("New content: \(updatedNote.note ?? "")")
    print("Is pinned: \(updatedNote.isPinned)")
case .failure(let failure):
    print("Failed to update note: \(failure.error.message)")
}

Get Note Detail

val result = zync.notes.getNoteDetail("9e4f2c8d-1a3b-4c5d-8e9f-2a3b4c5d6e7f")

when (result) {
    is GetNoteResult.Success -> {
        val note = result.note
        println("Note found: ${note.note}")
        println("Created: ${note.createdAt}")
        println("Last updated: ${note.updatedAt}")
        println("Attachments: ${note.attachments?.size ?: 0}")
        println("User mentions: ${note.userMentions?.size ?: 0}")

        // Access associated objects
        note.associatedJob?.let { job ->
            println("Associated job: ${job.jobNumber}")
        }

        // Access creator information
        note.createdBy?.let { creator ->
            println("Created by: ${creator.displayName}")
        }
    }
    is GetNoteResult.Failure -> {
        println("Error retrieving note: ${result.error.message}")
    }
}
let result = try await zync.notes.getNoteDetail("9e4f2c8d-1a3b-4c5d-8e9f-2a3b4c5d6e7f")

switch onEnum(of: result) {
case .success(let success):
    let note = success.note
    print("Note found: \(note.note ?? "")")
    print("Created: \(note.createdAt)")
    print("Last updated: \(note.updatedAt)")
    print("Attachments: \(note.attachments?.count ?? 0)")
    print("User mentions: \(note.userMentions?.count ?? 0)")

    // Access associated objects
    if let job = note.associatedJob {
        print("Associated job: \(job.jobNumber ?? "")")
    }

    // Access creator information
    if let creator = note.createdBy {
        print("Created by: \(creator.displayName ?? "")")
    }
case .failure(let failure):
    print("Error retrieving note: \(failure.error.message)")
}

Get Notes

import zync.api.sync.models.ZuperModule
import zync.public.notes.models.NotesResult
import zync.api.common.errors.ZyncError

val result = zync.notes.getNotes(
    module = ZuperModule.JOB,
    moduleUid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
)

when (result) {
    is NotesResult.Success -> {
        val notes = result.notes
        val totalCount = result.totalCount

        println("Loaded ${notes.size} notes (Total: $totalCount)")

        notes.forEach { note ->
            println("📝 ${note.note ?: "No content"}")
            println("   Type: ${note.noteType}")
            println("   Created: ${note.createdAt}")
            println("   Last updated: ${note.updatedAt}")
            println("   Pinned: ${note.isPinned}")
            println("   Visibility: ${note.noteVisibilityType}")

            // Show attachments
            note.attachments?.let { attachments ->
                if (attachments.isNotEmpty()) {
                    println("   Attachments (${attachments.size}):")
                    attachments.forEach { attachment ->
                        println("     - ${attachment.attachmentName}")
                    }
                }
            }

            // Show user mentions
            note.userMentions?.let { mentions ->
                if (mentions.isNotEmpty()) {
                    println("   Mentions: ${mentions.joinToString(", ")}")
                }
            }

            println("   ---")
        }
    }
    is NotesResult.Failure -> {
        println("Error loading notes: ${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.notes.getNotes(
    module: .job,
    moduleUid: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
)

switch onEnum(of: result) {
case .success(let success):
    let notes = success.notes
    let totalCount = success.totalCount

    print("Loaded \(notes.count) notes (Total: \(totalCount))")

    for note in notes {
        print("📝 \(note.note ?? "No content")")
        print("   Type: \(note.noteType)")
        print("   Created: \(note.createdAt)")
        print("   Last updated: \(note.updatedAt)")
        print("   Pinned: \(note.isPinned)")
        print("   Visibility: \(note.noteVisibilityType)")

        // Show attachments
        if let attachments = note.attachments, !attachments.isEmpty {
            print("   Attachments (\(attachments.count)):")
            for attachment in attachments {
                print("     - \(attachment.attachmentName ?? "")")
            }
        }

        // Show user mentions
        if let mentions = note.userMentions, !mentions.isEmpty {
            print("   Mentions: \(mentions.joined(separator: ", "))")
        }

        print("   ---")
    }
case .failure(let failure):
    print("Error loading notes: \(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)")
        }
    }
}

Delete Note

The SDK implements intelligent delete logic that handles notes differently based on their origin and sync status.

Smart Delete Behavior

Locally Created Notes (Hard Delete):

  • Notes created offline that haven’t been synced to server
  • Immediately removed from local database
  • No server synchronization needed
  • Pending CREATE operations are cancelled

Server-Synced Notes (Soft Delete):

  • Notes that exist on the server
  • Marked as deleted locally and queued for server sync
  • Preserves data integrity during offline periods
  • Synchronized to server when connectivity is available

Delete Examples

val result = zync.notes.deleteNote("9e4f2c8d-1a3b-4c5d-8e9f-2a3b4c5d6e7f")

when (result) {
    is DeleteNoteResult.Success -> {
        println("Note deleted successfully")
        // Note: The SDK automatically determined whether to use
        // hard delete (local note) or soft delete (server-synced note)
    }
    is DeleteNoteResult.Failure -> {
        println("Failed to delete note: ${result.error.message}")
    }
}
let result = try await zync.notes.deleteNote("9e4f2c8d-1a3b-4c5d-8e9f-2a3b4c5d6e7f")

switch onEnum(of: result) {
case .success:
    print("Note deleted successfully")
    // Note: The SDK automatically determined whether to use
    // hard delete (local note) or soft delete (server-synced note)
case .failure(let failure):
    print("Failed to delete note: \(failure.error.message)")
}

Delete Logic Flow

  1. Check Note Origin: SDK determines if note was created locally or exists on server
  2. Local Notes: Hard delete from database + cancel pending operations
  3. Server Notes: Mark as deleted + record DELETE operation for sync
  4. Cleanup: Remove associated attachments and relationships as needed

Automatic Behavior: You don’t need to worry about which delete strategy to use - the SDK automatically chooses the appropriate method based on the note’s sync status.

Refresh Note Data

Mark Note as Stale

Use this method to mark a note as stale, which triggers a refresh on the next access to [getNoteDetail]. The method marks the specified note as needing refresh, causing the next call to fetch the latest version from the server and update the local cache. This is particularly useful after making server-side changes (like updating note visibility) or when you need to ensure fresh data for critical operations.

// Mark note as stale to trigger refresh on next access
zync.notes.markNoteAsStale("9e4f2c8d-1a3b-4c5d-8e9f-2a3b4c5d6e7f")

println("Note marked as stale - will fetch latest data on next access")

// The note will be refreshed from server on next getNoteDetail call
// Subsequent calls to getNoteDetail will return the refreshed data
val refreshedNote = zync.notes.getNoteDetail("9e4f2c8d-1a3b-4c5d-8e9f-2a3b4c5d6e7f")
// Mark note as stale to trigger refresh on next access
await zync.notes.markNoteAsStale("9e4f2c8d-1a3b-4c5d-8e9f-2a3b4c5d6e7f")

print("Note marked as stale - will fetch latest data on next access")

// The note will be refreshed from server on next getNoteDetail call
// Subsequent calls to getNoteDetail will return the refreshed data
let refreshedNote = try await zync.notes.getNoteDetail("9e4f2c8d-1a3b-4c5d-8e9f-2a3b4c5d6e7f")

When to Use: Call markNoteAsStale after making server-side changes that don’t go through the SDK (like API calls to change visibility), when you suspect the local cache may be outdated, or after receiving push notifications about note updates.

Offline-First Behavior

The Zync SDK implements a sophisticated offline-first pattern for notes, 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 notes

Background Delta Sync

// First call - performs initial sync
val initialResult = zync.notes.getNotes(
    module = ZuperModule.JOB,
    moduleUid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
)
// User waits for sync to complete

// Subsequent calls - immediate response + background sync
val cachedResult = zync.notes.getNotes(
    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.notes.getNotes(
    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.notes.getNotes(
    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

Sync State Management

The SDK automatically tracks sync metadata for each module:

  • Sync Status: IN_PROGRESS, COMPLETED, or ERROR
  • Last Sync Time: Timestamp for delta sync filtering
  • Total Count: Cached count for immediate responses
  • Error Count: Automatic retry logic for failed syncs
  • Initial Sync Flag: Optimization flag for first-time vs subsequent calls

Network Resilience

Offline Operations:

  • All CRUD operations 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.

Performance and Sync Optimizations

The SDK includes several advanced optimizations to ensure optimal performance and efficient data synchronization.

Bulk Loading Optimizations

Attachment Loading:

  • Notes with multiple attachments are loaded using optimized bulk queries
  • Attachment tags are populated in batch operations
  • Minimizes database round trips for better performance

Relationship Management:

  • Note-attachment relationships are handled in bulk
  • Duplicate relationships are automatically cleaned up
  • Smart relationship diffing for updates

CRUD Operation Smart Merging

Conflict Prevention:

  • Multiple updates to the same note are merged into single operations
  • UPDATE operations on locally created notes are merged into CREATE operations
  • Prevents server errors from attempting to update non-existent entities

Operation Dependencies:

  • Attachment operations are properly sequenced with note operations
  • Dependency tracking ensures operations execute in correct order
  • Prevents data consistency issues during sync

Sync Performance Features

Delta Sync Optimization:

// Background delta sync only fetches notes updated since last sync
// Uses efficient API filtering: updatedAfter = lastSyncTimestamp
// Minimizes data transfer and processing time

Transaction Optimization:

  • Database operations use atomic transactions for consistency
  • Bulk operations are batched to reduce transaction overhead
  • Rollback protection ensures data integrity on failures

Memory Management:

  • Lazy attachment loading prevents memory bloat
  • Efficient database cursors for large result sets
  • Smart caching with automatic cleanup

Smart Relationship Handling

Attachment Association:

// SDK automatically handles complex attachment relationships
val noteWithAttachments = ZyncAddNoteContent(
    note = "Equipment inspection report",
    attachments = listOf(
        // Mix of existing attachments and new file paths
        ZyncNoteAttachment(
            attachmentUid = "existing-attachment-uid",
            attachment = "file:///path/to/existing/photo.jpg",
            attachmentName = "existing_photo.jpg",
            attachmentType = "IMAGE",
            attachmentSize = 1536000L,
            attachmentDescription = "Existing attachment",
            tags = listOf("existing"),
            thumbnailUrl = null
        ),
        ZyncNoteAttachment(
            attachmentUid = "new-attachment-uid",
            attachment = "file:///path/to/new/photo.jpg",
            attachmentName = "new_photo.jpg",
            attachmentType = "IMAGE",
            attachmentSize = 2048000L,
            attachmentDescription = "New attachment",
            tags = listOf("new"),
            thumbnailUrl = null
        )
    ),
    // ... other properties
)

// Behind the scenes:
// - Validates existing attachments exist
// - Creates new attachments from file paths
// - Establishes proper note-attachment relationships
// - Handles CRUD operation dependencies automatically

User Mention Processing:

  • User mentions are validated and processed efficiently
  • Associated user data is cached to prevent repeated lookups
  • Cleanup of invalid or deleted user references

Performance Monitoring

The SDK automatically tracks performance metrics:

  • Sync Duration: Time taken for initial and delta syncs
  • Operation Counts: Number of notes processed per sync
  • Error Rates: Failed operations with automatic retry logic
  • Cache Hit Rates: Efficiency of local cache usage

Best Performance: The SDK is optimized for typical usage patterns. For best performance, let the SDK handle sync timing automatically rather than forcing manual refreshes.

Best Practices

When to Use Each Method

Use getNotes() for:

  • Loading note lists for UI display
  • Regular data access patterns
  • Letting the SDK handle sync automatically

Use getNoteDetail() for:

  • Accessing individual note content
  • Displaying note details
  • Cached note retrieval

Use markNoteAsStale() when:

  • You’ve made server-side changes outside the SDK
  • You suspect local cache is outdated
  • After receiving push notifications about note updates
  • Following online-only visibility changes

Use addNote() and updateNote() for:

  • All local note creation and modification
  • Offline-first user interactions
  • Batch operations that should sync when online

Optimal Usage Patterns

Efficient Note Loading

class NotesViewModel {
    private var cachedNotes: List<ZyncNote> = emptyList()
    private var isInitialLoad = true
    suspend fun loadNotes() {
        val result = zync.notes.getNotes(
            module = ZuperModule.JOB,
            moduleUid = jobUid
        )

        when (result) {
            is NotesResult.Success -> {
                cachedNotes = result.notes
                // First load takes longer (initial sync)
                // Subsequent loads are instant (cached + background sync)
                isInitialLoad = false
                updateUI(result.notes)
            }
            is NotesResult.Failure -> {
                handleError(result.error)
            }
        }
    }

    // Only use markNoteAsStale when you know data might be outdated
    suspend fun refreshSpecificNote(noteUid: String) {
        zync.notes.markNoteAsStale(noteUid)
        // Reload notes to get updated data
        loadNotes()
    }
}

Offline-First Operations

// Create notes offline - they'll sync automatically
suspend fun createInspectionNote(jobUid: String, photos: List<String>) {
    val noteContent = ZyncAddNoteContent(
        note = "Equipment inspection completed",
        noteType = "TEXT",
        canNotifyOthers = true,
        noteVisibilityType = ZyncNoteVisibilityType.PUBLIC,
        attachments = photos.mapIndexed { index, photoPath ->
            ZyncNoteAttachment(
                attachmentUid = "photo-${System.currentTimeMillis()}-$index",
                attachment = photoPath,
                attachmentName = "photo_$index.jpg",
                attachmentType = "IMAGE",
                attachmentSize = 2048576L,
                attachmentDescription = "Inspection photo $index",
                tags = listOf("inspection", "equipment"),
                thumbnailUrl = null
            )
        },
        primaryModule = ZyncNotePrimaryModule("job", jobUid),
        isPinned = false,
        geoCoordinates = getCurrentLocation()
    )
    val result = zync.notes.addNote(content = noteContent)
    // Note 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
  • Create and update notes optimistically (offline-first)
  • Batch attachment operations with note operations
  • Use background sync for keeping data fresh
  • Use updatedAt to track note modifications and sort by recency

Don’ts ❌

  • Don’t call markNoteAsStale() frequently or on every load
  • Don’t force manual sync unless absolutely necessary
  • Don’t ignore offline capabilities - embrace offline-first patterns
  • Don’t create separate attachment operations - include them with notes

Error Handling Best Practices

suspend fun handleNoteOperations() {
    try {
        val result = zync.notes.getNotes(module, moduleUid)
        when (result) {
            is NotesResult.Success -> {
                // Handle success
                displayNotes(result.notes)
            }
            is NotesResult.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 notes. Using cached data.")
                        loadCachedData()
                    }
                }
            }
        }
    } catch (exception: Exception) {
        // Handle unexpected errors gracefully
        logError(exception)
        showGenericErrorMessage()
    }
}

Memory and Resource Management

  • Lazy Loading: The SDK loads attachments lazily to conserve memory
  • Automatic Cleanup: Database cleanup happens automatically during sync
  • Resource Optimization: File attachments are managed efficiently with deduplication

Security Considerations

  • Offline Security: Notes are stored securely in encrypted local database
  • Sync Security: All network operations use secure connections
  • Data Privacy: User mentions and sensitive 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.


Copyright © 2025 Zuper Inc. All rights reserved. This software is proprietary and confidential.