Search Docs
Loading...
Skip to content

Postcard Editor in Android

Professional postcard editing for your Android app—create and personalize postcards and greeting cards. Runs entirely on the mobile device with no server dependencies.

Postcard Editor starter kit screenshot

10 mins
estimated time
Download
GitHub

Pre-Requisites#

This guide assumes basic familiarity with Android and Kotlin. You will need:

  • Latest Android Studio
  • Kotlin: 1.9.10 or later
  • Gradle: 8.4 or later
  • Android: 7.0+ (API level 24+)

Get Started#

Start with a complete, runnable Android starter kit project.

Step 1: Clone the Repository#

Terminal window
git clone -b v1.73.0 https://github.com/imgly/starterkit-postcard-editor-android.git
cd starterkit-postcard-editor-android

Step 2: Open and Run#

Create and launch a new android emulator or use an existing one or connect a physical device with USB Debugging on.

Open the project in Android Studio, sync gradle via File -> Sync Project With Gradle Files and run the app module from UI, or use:

Terminal window
./gradlew app:installDebug

The sample app launches MainActivity that has “Launch Editor” button. Clicking it launches EditorActivity:

starter-kit/src/main/kotlin/ly/img/editor/configuration/postcard/EditorActivity.kt
package ly.img.editor.configuration.postcard
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import ly.img.editor.Editor
import ly.img.editor.core.configuration.EditorConfiguration
import ly.img.editor.core.configuration.remember
/**
* Encapsulated editor to be used in legacy activity navigation.
* Delete this file if you are using jetpack compose navigation.
*/
class EditorActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// This is required to remove the default action bar on top.
setTheme(android.R.style.Theme_Material_Light_NoActionBar)
// This is required, so that the editor is displayed full screen on relatively older devices.
enableEdgeToEdge()
setContent {
Editor(
license = null, // pass null or empty for evaluation mode with watermark
configuration = {
EditorConfiguration.remember(::PostcardConfigurationBuilder)
},
onClose = {
// Finish the activity, potentially handle errors.
finish()
},
)
}
}
}

The full implementation of the starter kit lives in the starter-kit/ folder:

starter-kit/
├── build.gradle.kts # Starter kit library module config, includes ly.img:editor dependency
└── src/main/
├── AndroidManifest.xml # Starter kit manifest file, may contain permissions
├── assets/
│ └── scene/
│ └── postcard.scene # Default postcard scene that should be loaded
└── kotlin/ly/img/editor/configuration/postcard/
├── EditorActivity.kt # Encapsulated editor for legacy navigation. Delete if you use jetpack compose navigation
├── PostcardConfigurationBuilder.kt # Editor configuration logic encapsulated in 1 place
├── callback/
│ ├── OnCreate.kt # Editor initialization logic
│ ├── OnExport.kt # Export flow and handling
│ └── OnLoaded.kt # Post onCreate logic
├── component/
│ ├── CanvasMenu.kt # Canvas Menu component configuration
│ ├── Dock.kt # Dock component configuration
│ ├── InspectorBar.kt # Inspector Bar component configuration
│ ├── NavigationBar.kt # Navigation Bar component configuration
│ └── Overlay.kt # Overlay component configuration
└── ext
└── Engine.kt # Engine extension functions required by the starter kit

Starter Kit as a Dynamic Feature#

Since starter-kit folder is an android library module, it is possible to turn it into a dynamic feature. This can be helpful if you want to lazy load the editor in order to reduce the download size of your app.

See Bundle Size for more details.

Configuring the Starter Kit#

The starter kit that we provide contains a very generic structure and behavior, however we understand that every customer wants to configure it according to their needs. The good thing is that the starter kit implementation is part of your codebase and you can configure, add/remove/modify functionality as you wish. In addition, you may want to configure the editor based on your business logic, i.e. restore the scene file from previous edits, display different dock items for different users etc.

This example demonstrates on how to pass, store and use external parameters in the starter kit. First, declare a new property to the builder class:

...
import android.net.Uri
@Stable
class PostcardConfigurationBuilder : BasicConfigurationBuilder() {
/**
* The scene uri that should be loaded in onCreate if not null.
* Note that editorContext.mutableStateOf is used to store mutable objects in the editor scope that survive configuration changes.
*/
var sceneUri: Uri? by editorContext.mutableStateOf(
key = "your.package.name.state.sceneUri",
initial = null,
)
...
}

Next, read your external parameter from activity intent extras (or jetpack compose screen arguments) and assign to the property of the builder:

import androidx.core.net.toUri
class EditorActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
val sceneUri = intent.getStringExtra("sceneUri")?.toUri()
Editor(
license = null, // pass null or empty for evaluation mode with watermark
configuration = {
EditorConfiguration.remember(::PostcardConfigurationBuilder) {
sceneUri = sceneUri
}
},
onClose = {
// Finish the activity, ignore any errors.
finish()
},
)
}
}

Finally, make use of the property. The scene loading logic is located at OnCreate.kt file, as part of onCreate implementation (see next section for more details). We modify this function to load the sceneUri instead if the value is not null:

starter-kit/src/main/kotlin/ly/img/editor/configuration/postcard/callback/OnCreate.kt
suspend fun PostcardConfigurationBuilder.onCreateScene() {
sceneUri?.let { safeSceneUri ->
// Load the sceneUri if it's not null
getOrLoadScene(sceneUri = safeSceneUri)
} ?: run {
// Otherwise stick to the default behavior of the starter kit
getOrLoadScene(sceneUri = "file:///android_asset/scene/postcard.scene".toUri())
}
}

Set Up a Scene#

The scene setup logic is located at OnCreate.kt file, as part of onCreate implementation:

starter-kit/src/main/kotlin/ly/img/editor/configuration/postcard/callback/OnCreate.kt
suspend fun PostcardConfigurationBuilder.onCreateScene() {
getOrLoadScene(sceneUri = "file:///android_asset/scene/postcard.scene".toUri())
}

getOrCreateSceneFromImage is a helper that loads an existing scene if available, or initializes one from file:///android_asset/scene/postcard.scene when no scene is active.

CE.SDK offers multiple ways to load scene into the editor. Choose the method that matches your use case:

// Load from an image uri - creates a new scene with the image
editorContext.engine.scene.createFromImage(imageUri = "https://example.com/photo.jpg".toUri())
// Load from a template archive - restores a previously saved project
editorContext.engine.scene.loadArchive(archiveUri = "https://example.com/template.zip".toUri())
// Create a blank canvas - starts with an empty design scene
editorContext.engine.scene.create()
// Load from a scene file - restores a scene from .scene file
editorContext.engine.scene.load(sceneUri = "https://example.com/saved.scene".toUri())

Customize Assets#

The asset source setup is located in OnCreate.kt as part of onCreate implementation. Enable or disable individual sources:

starter-kit/src/main/kotlin/ly/img/editor/configuration/postcard/callback/OnCreate.kt
suspend fun PostcardConfigurationBuilder.onLoadAssetSources() {
// Load asset sources in parallel from content.json files
coroutineScope {
listOf(
DefaultAssetSource.STICKER.key,
DefaultAssetSource.VECTOR_PATH.key,
DefaultAssetSource.FILTER_LUT.key,
DefaultAssetSource.FILTER_DUO_TONE.key,
DefaultAssetSource.CROP_PRESETS.key,
DefaultAssetSource.EFFECT.key,
DefaultAssetSource.BLUR.key,
DefaultAssetSource.TYPEFACE.key,
DemoAssetSource.IMAGE.key,
DemoAssetSource.TEXT_COMPONENTS.key,
).forEach { assetSource ->
launch {
val baseUri = editorContext.baseUri
editorContext.engine.populateAssetSource(
id = assetSource,
jsonUri = "$baseUri/$assetSource/content.json".toUri(),
replaceBaseUri = baseUri,
)
}
}
}
// Load local asset sources
editorContext.engine.asset.addLocalSource(
sourceId = DemoAssetSource.IMAGE_UPLOAD.key,
supportedMimeTypes = listOf(
"image/jpeg",
"image/png",
"image/heic",
"image/heif",
"image/svg+xml",
"image/gif",
"image/bmp",
),
)
// Register gallery asset sources
listOf(
AssetSourceType.GalleryAllVisuals,
AssetSourceType.GalleryImage,
AssetSourceType.GalleryVideo,
).forEach { type ->
editorContext.engine.asset.addSource(
source = SystemGalleryAssetSource(
context = editorContext.engine.applicationContext,
type = type,
),
)
}
SystemGalleryPermission.setMode(systemGalleryConfiguration)
// Register text asset source
TypefaceProvider().provideTypeface(
engine = editorContext.engine,
name = "Roboto",
)?.let {
val textAssetSource = TextAssetSource(engine = editorContext.engine, typeface = it)
editorContext.engine.asset.addSource(textAssetSource)
}
}

For production deployments, self-hosting assets is required—the IMG.LY CDN is intended for development only. See Serve Assets for downloading assets, configuring baseUri and excluding unused sources to optimize load times.

Customize Export Functionality#

Export handling logic is located in OnExport.kt as part of onExport callback.

onExportByteBuffer controls what should be exported from the scene. It can be a scene or set of design blocks. For postcard editor, it makes sense to export the scene to a PDF content. export is a helper function that calls editorContext.engine.block.export under the hood:

starter-kit/src/main/kotlin/ly/img/editor/configuration/postcard/callback/OnExport.kt
suspend fun PostcardConfigurationBuilder.onExportByteBuffer(): ByteBuffer = export(
block = requireNotNull(editorContext.engine.scene.get()),
mimeType = MimeType.PDF,
)

onPostExport controls what should happen to the exported ByteBuffer content. You can upload the result to your server, save it to the device gallery or simply close the editor via editorContext.eventHandler.send(EditorEvent.CloseEditor()). Check writeToFile, shareFile and shareUri helper functions for potential implementations:

starter-kit/src/main/kotlin/ly/img/editor/configuration/postcard/callback/OnExport.kt
suspend fun PostcardConfigurationBuilder.onPostExport(byteBuffer: ByteBuffer) {
val file = writeToFile(byteBuffer = byteBuffer, mimeType = MimeType.PDF)
shareFile(file = file, mimeType = MimeType.PDF)
}

Customize (Optional)#

Base Uri#

The starter kit does not make any baseUri configuration, which means it points to https://cdn.img.ly/packages/imgly/cesdk-engine/1.73.0/assets. If you want to store them in your own CDN or locally, assets can be accessed via zip file. For example, if you want to store them locally, unzip the content and place at starter-kit/src/main/assets:

import androidx.compose.runtime.Composable
import androidx.core.net.toUri
import ly.img.editor.Editor
import ly.img.editor.configuration.postcard.PostcardConfigurationBuilder
import ly.img.editor.core.configuration.EditorConfiguration
import ly.img.editor.core.configuration.remember
@Composable
fun EditorBaseUriScreen(onClose: (error: Throwable?) -> Unit) {
Editor(
license = null, // pass null or empty for evaluation mode with watermark
baseUri = "file:///android_asset".toUri(), // this points to android assets
configuration = {
EditorConfiguration.remember(::PostcardConfigurationBuilder)
},
onClose = onClose,
)
}

UI Mode#

CE.SDK supports light and dark ui modes out of the box, plus automatic system preference detection. Switch between themes programmatically:

import androidx.compose.runtime.Composable
import ly.img.editor.Editor
import ly.img.editor.EditorUiMode
import ly.img.editor.configuration.postcard.PostcardConfigurationBuilder
import ly.img.editor.core.configuration.EditorConfiguration
import ly.img.editor.core.configuration.remember
@Composable
fun EditorUiModeScreen(onClose: (error: Throwable?) -> Unit) {
Editor(
license = null, // pass null or empty for evaluation mode with watermark
uiMode = EditorUiMode.SYSTEM, // EditorUiMode.SYSTEM, EditorUiMode.LIGHT, EditorUiMode.DARK
configuration = {
EditorConfiguration.remember(::PostcardConfigurationBuilder)
},
onClose = onClose,
)
}

See Theming for more details.

Native Android Canvas#

CE.SDK supports rendering on two of the most popular native android views that allow GPU rendering: TextureView and SurfaceView:

import androidx.compose.runtime.Composable
import ly.img.editor.Editor
import ly.img.editor.configuration.postcard.PostcardConfigurationBuilder
import ly.img.editor.core.configuration.EditorConfiguration
import ly.img.editor.core.configuration.remember
import ly.img.editor.core.engine.EngineRenderTarget
@Composable
fun EditorRenderTargetScreen(onClose: (error: Throwable?) -> Unit) {
Editor(
license = null, // pass null or empty for evaluation mode with watermark
engineRenderTarget = EngineRenderTarget.SURFACE_VIEW, // EngineRenderTarget.SURFACE_VIEW, EngineRenderTarget.TEXTURE_VIEW
configuration = {
EditorConfiguration.remember(::PostcardConfigurationBuilder)
},
onClose = onClose,
)
}

Localization#

See Localization for supported languages, adding support to new languages and replacing existing keys.

UI Layout#

All the configurable components are located at starter-kit/src/main/kotlin/ly/img/editor/configuration/postcard/component:

  • CanvasMenu.kt - see Canvas Menu for full configuration options.
  • Dock.kt - see Dock for full configuration options.
  • InspectorBar.kt - see Inspector Bar for full configuration options.
  • NavigationBar.kt - see Navigation Bar for full configuration options.
  • Overlay.kt - see Overlay for full configuration options.

Troubleshooting#

Editor doesn’t load#

  • Check onCreate: Ensure onCreate callback loads a scene and no coroutine is stuck infinitly
  • Verify the baseURL: Assets must be accessible from the CDN or your self-hosted location
  • Check logcat errors: Look for error in Android logcat

Assets don’t appear#

  • Check network requests: Make sure the device/emulator is connected to the internet
  • Self-host assets for production: See Serve Assets to host assets on your infrastructure
  • Check logcat errors: Look for error in Android logcat

Export fails or produces blank images#

  • Wait for content to load: Ensure images are fully loaded before exporting
  • Check logcat errors: Look for error in Android logcat

Watermark appears in production#

  • Add your license key: Set the license property in your configuration
  • Sign up for a trial: Get a free trial license at img.ly/forms/free-trial

Next Steps#