Skip to main content

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.

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.