Migration Guides

Migration guides for Kotlin SDKs.

Migration guide for v1.x.x to v2.x.x

This guide explains how to migrate your apps after the v2 release’s package and artifact restructure. It covers three paths: staying on v1 via legacy modules, migrating from v1 to v2, and moving from v2-beta to the final v2.

For feature-level differences and what’s new, see: What’s new in 2.0.0


What changed at a glance

  • Dependencies: use standard io.joyfill:* artifacts for v2, or io.joyfill:legacy-* to remain on v1 (details, stay on v1)
  • Package names: v2-beta code under joyfill2 moved to joyfill in v2 — rename imports from joyfill2joyfill (details)
  • Event API: legacy FieldEvent has been replaced by ComponentEvent.* — use ComponentEvent.FieldEvent (standalone fields) and ComponentEvent.CellEvent (table cells) (details)
  • Behavior notes: most imports remain identical for v1→v2 because the package is still joyfill; some v1 UI components may not exist in v2 — check v2 docs for alternatives (details)

Dependencies changes

  • Replace legacy artifacts with the standard ones:
    • io.joyfill:legacy-compose → io.joyfill:compose
    • io.joyfill:legacy-models → io.joyfill:models
    • io.joyfill:legacy-builder → io.joyfill:builder
    • io.joyfill:legacy-api → io.joyfill:api

Gradle example (Kotlin DSL)

// BEFORE (v1)
dependencies {
    implementation("io.joyfill:legacy-compose:<version>")
    implementation("io.joyfill:legacy-models:<version>")
    implementation("io.joyfill:legacy-builder:<version>")
    implementation("io.joyfill:legacy-api:<version>")
}

// AFTER (v2)
dependencies {
    implementation("io.joyfill:compose:<version>")
    implementation("io.joyfill:models:<version>")
    implementation("io.joyfill:builder:<version>")
    implementation("io.joyfill:api:<version>")
}

Imports and usage

  • Keep using joyfill.* imports.
  • Example usage:
Form(
    editor = rememberEditor(document = doc),
    mode = Mode.fill,
)

FieldEvent → ComponentEvent.* (events)

Summary

  • v1 used a single FieldEvent for all field callbacks.
  • v2 unifies events under ComponentEvent.* with two concrete types:
    • ComponentEvent.FieldEvent<E> for standalone fields (text, number, image, signature, etc.)
    • ComponentEvent.CellEvent<E> for table cells (row-aware events)
  • Handler parameter types changed accordingly in Form/components.

Why this changed

  • The v2 architecture standardizes how components emit and consume events and makes table events first-class by including row/column context.

API mapping

  • v1 (legacy): joyfill.FieldEvent
  • v2: joyfill.ComponentEvent.FieldEvent and joyfill.ComponentEvent.CellEvent

Key properties mapping

  • Common in both versions:
    • fieldId, fieldIdentifier, pageId, id (document id), identifier (document identifier), fileId, fieldPositionId
  • v2 additions/notes:
    • source: the typed editor instance (e.g., ImageEditor, TextEditor)
    • multi: available when the underlying component is a file component (e.g., image with multi-upload)
    • Cell-only: rowIds, columnId, schemaId, parentPath

Before → After examples

  1. General field handlers (change/focus/blur)
  • v1 (legacy Form):
Form(
    // ...
    onFieldChange = { e: FieldEvent -> /* use e.fieldId, e.pageId, ... */ },
    onFocus = { e: FieldEvent -> /* ... */ },
    onBlur = { e: FieldEvent -> /* ... */ },
)
  • v2 (Form/components):
Form(
    // ...
    onFieldChange = { e: ComponentEvent<*> ->
        when (e) {
            is ComponentEvent.FieldEvent<*> -> { /* standalone field */ }
            is ComponentEvent.CellEvent<*>  -> { /* table cell */ }
        }
    },
    onFocus = { e: ComponentEvent<*> -> /* same pattern as above */ },
    onBlur  = { e: ComponentEvent<*> -> /* same pattern as above */ },
)
  1. File upload/capture handlers (image/signature/barcode)
  • v1:
Form(
    onUpload = { e: FieldEvent -> listOf("https://.../file1") },
    onCapture = { e: FieldEvent -> "barcode-or-text" },
)
  • v2:
Form(
    onUpload = { e: ComponentEvent<joyfill.editors.file.AbstractFileEditor> ->
        // Optional: narrow by editor type
        when (val src = e.source) {
            is joyfill.editors.image.ImageEditor -> listOf("https://.../img1")
            is joyfill.editors.signature.SignatureEditor -> listOf("https://.../sig1")
            else -> emptyList()
        }
    },
    onCapture = { e: ComponentEvent<joyfill.editors.components.AbstractCompStringEditor> ->
        // Example for barcode/text-capable components
        when (e.source) {
            is joyfill.editors.barcode.BarcodeEditor -> "scanned-barcode"
            else -> null
        }
    },
)
  1. Table cell events
  • v2 introduces CellEvent for row-aware callbacks used by table components and their editors:
val handler: (ComponentEvent.CellEvent<*>) -> Unit = { e ->
    val rowIds = e.rowIds  // one or more row ids affected
    val columnId = e.columnId
    val fieldId = e.fieldId
    // handle per-row updates, analytics, etc.
}

Minimal refactor recipe

  • Replace callback parameter types:
    • FieldEventComponentEvent<*> when used at the Form level
    • For table-specific handlers: prefer ComponentEvent.CellEvent<*>
    • For single-field components: prefer ComponentEvent.FieldEvent<*>
  • Update when branches to handle both FieldEvent and CellEvent where applicable.
  • If you previously relied on attachments in events: v2 surfaces file uploads through file editors and returns should come from your handler; attachments on events is not required in typical flows.

Runtime checks and generics

  • ComponentEvent is generic on the editor type (E : ComponentEditor). When you need editor-specific behavior, check e.source with is ImageEditor etc.

References


FAQ

Q: Why do both v1 and v2 have joyfill.Form? A: v1 code was moved into legacy-* modules but retained the exact same package names for easier migration. Which Form you get depends on whether your dependency is from legacy-* or from the standard modules.

Q: How do I verify I’m on v2?

  • Ensure you’re using the standard artifacts (no legacy- prefix).
  • Confirm your imports are joyfill.* and that you do NOT have joyfill2.* anywhere.

Q: Any behavioral differences?

  • Some components may be missing in v2 compared to v1, but v2 offers many new features and improved architecture. See the v2 docs for details.

Related Documentation


Important context

  • Prior to v2 release, we had:
    • v1 under the joyfill package
    • v2-beta under the joyfill2 package
  • With the v2 release:
    • v2 now lives under the joyfill package
    • Legacy v1 classes were moved into separate legacy modules, but kept the same joyfill package names for source compatibility
    • Practically, both legacy and v2 expose symbols like joyfill.Form. Which one you use depends on the dependency you add.

What this means for you

  • V1 → V2: swap your dependencies from legacy modules to v2 modules. Most imports remain identical because the package is still joyfill.
  • V2-beta → V2: rename your imports from joyfill2 to joyfill. No other code changes should be required.

Stay on v1 (Legacy Modules)

[!CAUTION] This is not RECOMMENDED. This is a temporary path for those who want to continue using v1 for a short period of time.

Summary

  • If you want to continue using the legacy v1 implementation for a period, switch your dependencies to the legacy-* artifacts.
  • Imports remain joyfill.* because legacy modules preserve the same package names.

Dependencies

  • Replace standard artifacts with legacy ones if you were previously on the standard artifacts but wish to remain on v1:
    • io.joyfill:composeio.joyfill:legacy-compose
    • io.joyfill:modelsio.joyfill:legacy-models
    • io.joyfill:builderio.joyfill:legacy-builder
    • io.joyfill:apiio.joyfill:legacy-api

Gradle example (Kotlin DSL)

// BEFORE (standard artifacts)
dependencies {
    implementation("io.joyfill:compose:<version>")
    implementation("io.joyfill:models:<version>")
    implementation("io.joyfill:builder:<version>")
    implementation("io.joyfill:api:<version>")
}

// AFTER (stay on v1 legacy)
dependencies {
    implementation("io.joyfill:legacy-compose:<version>")
    implementation("io.joyfill:legacy-models:<version>")
    implementation("io.joyfill:legacy-builder:<version>")
    implementation("io.joyfill:legacy-api:<version>")
}

Verification

  • Ensure your imports remain joyfill.* and not joyfill2.*.
  • Confirm you’re pulling legacy-* artifacts in your dependency tree.

Notes

  • This path is intended for temporary continuity. Plan to migrate to v2 soon.