Search Docs
Loading...
Skip to content

Form-Based Editing

Build custom Android form interfaces that populate template variables and image placeholders through the CreativeEngine API.

Generated Android form-based editing result showing updated workshop text and a replacement hero image

8 mins
estimated time
GitHub

Form-based editing turns template customization into structured data entry. Instead of asking users to edit blocks directly on the canvas, your Android UI collects values in native controls and applies them to a template with variables and placeholders.

Understanding Form-Based Editing#

Variables control text content. Image placeholders control replaceable image blocks. A form-based workflow maps each native input field to one of those template fields, validates the submitted values, and updates the engine so the template preview or export reflects the form state.

CE.SDK does not ship a dedicated Android form panel. If you want a full editor preview beside your form, use the Design Editor Starter Kit as the CE.SDK editor UI and drive the same Engine APIs from your own Compose controls.

Discovering Template Metadata#

Start from a loaded template that already contains text variables and image placeholders. Query the variable store and filter placeholder blocks to determine which form controls your Android UI needs.

val variableKeys = engine.variable.findAll().sorted()
val imagePlaceholders = engine.block.findByType(DesignBlockType.Graphic)
.filter { block ->
engine.block.isPlaceholderEnabled(block) && engine.block.supportsFill(block)
}
val placeholderNames = imagePlaceholders.map { block -> engine.block.getName(block) }

engine.variable.findAll() returns the variable keys currently stored in the engine. The placeholder lookup uses typed DesignBlockType.Graphic queries and checks isPlaceholderEnabled() so the form only targets blocks that the template author marked as replaceable.

Working with Variables#

Represent the values from your native controls in an app-owned data model. CE.SDK only receives the final strings and image URIs; TextField state, validation messages, and image pickers stay in your Android UI layer.

data class TemplateFormState(
val headline: String,
val subline: String,
val heroImageUri: Uri,
)

Reading Current Values#

Read existing variable values to prefill your form when a user opens a template that already contains default content.

val initialValues = variableKeys.associateWith { key -> engine.variable.get(key) }
val heroPlaceholder = imagePlaceholders.first { block ->
engine.block.getName(block) == "hero-image"
}
val currentHeroFill = engine.block.getFill(heroPlaceholder)
val initialHeroImageUri = engine.block.getUri(
block = currentHeroFill,
property = "fill/image/imageFileURI",
)

The same step can read the current image URI from a placeholder fill, which lets your UI show the currently assigned image before the user selects a replacement.

Updating Variables#

After your Compose controls produce a submitted form state, assign each text field to the matching variable key.

val submittedForm = TemplateFormState(
headline = "Launch Workshop",
subline = "Join the live product walkthrough.",
heroImageUri = Uri.parse("https://img.ly/static/ubq_samples/sample_4.jpg"),
)
engine.variable.set(key = "headline", value = submittedForm.headline)
engine.variable.set(key = "subline", value = submittedForm.subline)

Text blocks that reference {{headline}} or {{subline}} update from the variable store during preview and export.

Replacing Placeholder Content#

For image fields, locate the placeholder block, get its fill, and update the image URI stored on that fill.

val updatedHeroFill = engine.block.getFill(heroPlaceholder)
engine.block.setUri(
block = updatedHeroFill,
property = "fill/image/imageFileURI",
value = submittedForm.heroImageUri,
)
engine.block.resetCrop(heroPlaceholder)

Resetting the crop after the replacement keeps the placeholder framing consistent when the new image has different dimensions than the original asset.

Building the Form UI#

Build the visible form with normal Android UI primitives such as Compose TextField, image picker launchers, dropdowns, or validation labels. Keep that UI state in your app, then pass the submitted values into the mapping layer shown above.

The important boundary is that CE.SDK does not need to own the form controls. Your code discovers the editable template fields, shows matching native controls, and calls engine.variable.set() for text fields or engine.block.setUri() for image fields when the user changes content.

Error Handling#

Validate the form before export or before enabling a final action. Required text fields and image selections should be checked in your UI state before you mutate the template.

val missingRequiredFields = listOfNotNull(
"headline".takeIf { submittedForm.headline.isBlank() },
"subline".takeIf { submittedForm.subline.isBlank() },
"heroImageUri".takeIf { submittedForm.heroImageUri.toString().isBlank() },
)
check(missingRequiredFields.isEmpty()) {
"Missing required form fields: ${missingRequiredFields.joinToString()}"
}

Then export the populated page once the data is complete.

val pngData = engine.block.export(
block = page,
mimeType = MimeType.PNG,
)

Handle these cases in the same validation layer:

  • Missing variables: compare discovered keys with the fields your form requires.
  • Invalid images: check MIME type and URI availability before assigning a file to an image placeholder.
  • Missing placeholders: keep stable block names or metadata for required image fields.
  • Export failures: report which field or asset prevented the final output.

API Reference#

Method Description
engine.variable.findAll() List variable keys stored on the engine
engine.variable.get(key=_) Read the current value for a variable key
engine.variable.set(key=_, value=_) Set or update a text variable
engine.block.findByType(type=_) Find blocks by typed design-block type
engine.block.isPlaceholderEnabled(block=_) Check whether a block is enabled as a placeholder
engine.block.supportsFill(block=_) Check whether a block can carry a fill
engine.block.getName(block=_) Read the semantic name assigned to a block
engine.block.getFill(block=_) Get the fill block attached to a design block
engine.block.getUri(block=_, property=_) Read URI-backed block or fill properties such as image file URIs
engine.block.setUri(block=_, property=_, value=_) Update URI-backed block or fill properties such as image file URIs
engine.block.resetCrop(block=_) Reset crop values after replacing an image
engine.block.export(block=_, mimeType=_) Export the populated template page

Next Steps#