Build an Android Wishes and Greetings integration
This guide is for Android developers adding Adobe Express creation tools to a partner app. You'll install the Android Mobile SDK, initialize it with your application's identity, open the Wishes and Greetings module, and display a published PNG in your own screen. You need basic Kotlin and Android Activity knowledge; you don't need a previous Embed SDK integration.
You'll build one flow: open Wishes and Greetings → choose a template → personalize it in the editor → select Use image → display the PNG in your app. The examples use an AppCompat host, a third-party development configuration, and Base64 image output. This guide doesn't cover other SDK workflows or an iOS integration.
data-slots=text
data-variant=warning
PR-2062 prerelease and a partner-approved STAGE client configuration. Don't substitute the latest general SDK release without confirming that it includes this module, or treat the development configuration as production access. Obtain the SDK distribution details and client provisioning for your integration from your Adobe partner contact.Understand the SDK and host boundary
The Android Mobile SDK gives your app a Kotlin API for launching Adobe Express experiences. Wishes and Greetings provides a native template browser. Selecting a template opens the SDK's editor, which uses Android WebView. You don't build a separate web application or implement a WebView message bridge for this recipe.
Your host and the SDK have different responsibilities:
The distinction matters most at the end of the flow: receiving an image and dismissing a screen are separate operations. You'll handle both, without removing the editor from the publish callback.
Before you begin
Prepare a Kotlin Android app in Android Studio, access to the partner-provided SDK Maven distribution, and a client ID approved for this development integration.
Use these settings for the tutorial host:
minSdk to 28androidx.appcompat.app.AppCompatActivity and use supportFragmentManagercompileSdk = 36, matching the integration sample; install Android SDK Platform 36INTERNET and ACCESS_NETWORK_STATE permissionsDon't confuse the device API level with the compile SDK. The SDK's own compile setting is 35, and the transitive dependency set used for this recipe requires up to compile SDK 35. This tutorial uses 36 as the sample's consumer setting, not as a claim that every SDK integration requires 36. Your app's full dependency graph determines its final compile requirements.
Step 1 — Prepare the host screen
Start with the screen that should launch Wishes and Greetings and receive the result. Keep the host UI small so you can follow the integration boundary.
- Use an
AppCompatActivityfor the screen that owns the workflow. - Add a launch button, a status
TextView, and a resultImageViewto its content view. - Keep the launch button disabled until SDK initialization succeeds.
- Use your application's own package identity. You don't need to copy an engineering test app's package name to integrate the SDK.
The launch helper later in this guide replaces android.R.id.content and adds the browser transaction to the back stack. That lets the host screen remain the return destination. The result ImageView belongs to your host, not to the SDK Fragment.
Checkpoint: you have a host screen with a place to launch the workflow, report failures, and display the returned image. No SDK screen is open yet.
Step 2 — Install the Android SDK dependency
The Android SDK installation README describes the consumer installation pattern: add the Maven dependency to your app module, choose a version, sync Gradle, and import ExpressEmbedSdk. Apply those steps to the version provisioned for this module.
Configure dependency access
- Obtain the Maven repository URL and any required credentials through your partner provisioning process.
- Add that repository to your project's dependency-resolution configuration, normally in
settings.gradle.ktsunderdependencyResolutionManagement.repositories. - Load repository credentials from your approved local or CI secret mechanism rather than committing them in a build file.
The public README doesn't name a repository or promise unauthenticated artifact access. Don't assume that adding the coordinate to Maven Central is sufficient. Repository values such as SDK_MAVEN_REPOSITORY_URL, SDK_MAVEN_USERNAME, and SDK_MAVEN_TOKEN represent your provisioned settings, not SDK API parameters or required SDK-defined variable names.
Keep the two kinds of identity separate:
HostInfo when initializing the SDKAdd the module dependency and sync
- Open your app module's
build.gradle.kts. - Add the SDK to its
dependenciesblock using the Kotlin DSL entry for this recipe:
dependencies {
implementation("com.adobe.express.embed:embedsdk:PR-2062")
}
- Sync the project in Android Studio so Gradle resolves the SDK and its transitive dependencies.
- Confirm that the
com.adobe.express.embedsdk.ExpressEmbedSdkimport resolves in your Kotlin source. The initialization example in Step 3 includes this import.
The README expresses the dependency as com.adobe.express.embed:embedsdk:x.y.z and directs general SDK consumers to GitHub Releases for a release version. Here, PR-2062 replaces x.y.z because this guide covers that prerelease's Wishes and Greetings API. It isn't a general recommendation to use a prerelease for unrelated SDK features.
The dependency installs a compiled Android library and its dependency model. Cloning or updating an SDK Git source repository doesn't change the Maven artifact selected by your app. Likewise, an engineering test-app APK is an application, not the SDK dependency you add to your host. Keep the provisioned artifact version fixed while integrating this flow; prerelease artifacts can change without a new release-style version number.
Checkpoint: Gradle recognizes the SDK dependency, and your source can import ExpressEmbedSdk. If dependency resolution fails, address repository access or the supplied coordinate before writing launch code.
Step 3 — Initialize and retain the SDK
Initialization connects your app's identity and configuration to the SDK interface. The public entry point is ExpressEmbedSdk.initialize, which returns a CCEverywhereInterface synchronously. It isn't a JavaScript-style SDK loader or an initialization promise.
The helper uses these inputs:
hostInfoconfigParamsEnvironment.STAGE and locale en_USauthProviderAuthOption selecting AuthMode.DELAYEDappContextAdd the public initialization helper to your host's SDK integration code:
import android.content.Context
import com.adobe.express.embedsdk.AuthMode
import com.adobe.express.embedsdk.AuthOption
import com.adobe.express.embedsdk.CCEverywhereInterface
import com.adobe.express.embedsdk.ConfigParams
import com.adobe.express.embedsdk.Environment
import com.adobe.express.embedsdk.ExpressEmbedSdk
import com.adobe.express.embedsdk.HostInfo
import com.adobe.express.embedsdk.Version
// Call once per process. Supply the client ID approved for your STAGE integration.
fun initializeWishesSdk(context: Context, clientId: String): CCEverywhereInterface =
ExpressEmbedSdk.initialize(
hostInfo = HostInfo(clientId, "Wishes demo", Version(1, 0, 0)),
configParams = ConfigParams(env = Environment.STAGE, locale = "en_US"),
authProvider = { AuthOption(mode = AuthMode.DELAYED.value) },
appContext = context.applicationContext,
)
Pass the client ID approved for your app as clientId. Wishes demo and Version(1, 0, 0) are example host metadata, not the SDK's name or version. HostInfo uses the mobile platform category by default.
- Call the helper through an application-scoped holder during host startup.
- Store the returned
CCEverywhereInterfacefor subsequent launches. - Catch initialization exceptions and report them in the host status view.
- Enable the launch button only after the instance is available.
The helper itself doesn't cache an instance; your holder does. Don't initialize on every button tap or every Activity recreation. Reinitializing can close an existing SDK session, and concurrent initialization can fail.
This recipe explicitly selects delayed authentication for the third-party path. It doesn't pass a pre-signed-in user token or borrow an Adobe first-party sign-in handler. Delayed authentication isn't a promise that every editor operation needs no authentication, and selecting STAGE doesn't grant service entitlement. Keep this development setup aligned with your partner provisioning; production configuration is a separate agreement.
Checkpoint: the host retains one SDK interface and can report initialization failure without opening a broken workflow.
Step 4 — Connect the workflow callbacks
Before launching, create the Callbacks object that connects the SDK session to your host UI. Pass it to the launch helper in Step 5. The existing host wiring implements callback properties on a Callbacks object; it doesn't introduce a separate host editor workflow.
Use these hooks for this integration:
onLoadInitonCancelonErroronPublishonSessionFinishedThe sample maintains a host-owned publishReceived flag. It resets that flag before launch, sets it when onPublish arrives, and uses it in onSessionFinished to decide whether to dismiss the browser. This flag records a publish event, not successful image decoding. A missing or invalid image still needs a visible error message.
Dispatch view updates and FragmentManager operations to the main thread. Start decoding through the Activity's lifecycle coroutine scope and move the expensive work to an I/O dispatcher, as described in Step 8.
Step 5 — Configure and mount Wishes and Greetings
The workflow entry point is sdk.module.wishesAndGreetingsNative. It returns an EmbedSdkWishesAndGreetingsNativeFragment; your host mounts that Fragment.
The arguments divide the configuration into four concerns:
wishesAndGreetingsNativeDocConfiginitialCategoryId unsetwishesAndGreetingsNativeAppConfigonDismiss handlerexportConfigcontainerConfigYou don't need a category identifier to start. The default WishesAndGreetingsNativeDocConfig() uses initialCategoryId = null. If your integration later needs a preselected category, use an identifier supplied for that catalog rather than inventing one from its display label.
The app configuration also supports colorTheme, metaData, analyticsData, and appVersion. They aren't required for the path below. The sample selects ColorTheme.LIGHTEST; this public helper leaves the optional theme unset and focuses on the launch, export, and dismiss boundary.
Add the launch helper to your Activity integration code:
import androidx.appcompat.app.AppCompatActivity
import com.adobe.express.embedsdk.AssetDataType
import com.adobe.express.embedsdk.ButtonStyle
import com.adobe.express.embedsdk.CCEverywhereInterface
import com.adobe.express.embedsdk.Callbacks
import com.adobe.express.embedsdk.PublishAction
import com.adobe.express.embedsdk.PublishExportOption
import com.adobe.express.embedsdk.WishesAndGreetingsNativeAppConfig
import com.adobe.express.embedsdk.WishesAndGreetingsNativeDocConfig
import com.adobe.express.embedsdk.wishesandgreetings.ui.EmbedSdkWishesAndGreetingsNativeFragment
// Invoke on the main thread when FragmentManager state is not saved.
fun launchWishes(
activity: AppCompatActivity,
sdk: CCEverywhereInterface,
callbacks: Callbacks,
) {
val manager = activity.supportFragmentManager
if (manager.isStateSaved) return
val tag = EmbedSdkWishesAndGreetingsNativeFragment.FRAGMENT_TAG
val appConfig = WishesAndGreetingsNativeAppConfig(
callbacks = callbacks,
onDismiss = {
if (!manager.isStateSaved) {
manager.popBackStack(tag, androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE)
}
},
)
val fragment = requireNotNull(sdk.module).wishesAndGreetingsNative(
wishesAndGreetingsNativeDocConfig = WishesAndGreetingsNativeDocConfig(),
wishesAndGreetingsNativeAppConfig = appConfig,
exportConfig = listOf(
PublishExportOption(
id = "returnImage",
label = "Use image",
style = ButtonStyle(),
action = PublishAction(
target = "publish",
publishFileType = "image/png",
outputType = AssetDataType.BASE64,
closeTargetOnExport = true,
enableByDefault = true,
),
),
),
)
manager.beginTransaction()
.replace(android.R.id.content, fragment, tag)
.addToBackStack(tag)
.commit()
}
- Connect your launch button to this helper, passing the Activity, retained SDK interface, and callback object.
- Invoke it on the main thread while the Activity can accept FragmentManager transactions.
- Catch launch exceptions at the host event-handler boundary and report the failure in your status view.
- Prevent duplicate launches while the workflow is already open.
The saved-state guard intentionally returns without launching if FragmentManager.isStateSaved is true. It doesn't queue a launch. Keep a pending user action in host state if you need to retry after the Activity resumes; don't force a transaction after saved state.
data-slots=text
data-variant=success
Step 6 — Open a template in the SDK editor
Select a template in the browser. The SDK carries the selected template identifier into its editor and forwards the export configuration you supplied at launch.
This transition is part of the Wishes and Greetings workflow. Your partner app doesn't handle a public onTemplateSelected callback or call editDesign to open a second editor. In this prerelease, the composed Wishes workflow supports the third-party path, while calling editDesign directly as a third-party client remains unsupported.
With containerConfig unset, the editor opens over the browse surface using the default content container. Keep the hosting Activity alive while the editor is active. The normal close path returns to the browser; your dismiss handling determines when to return from the browser to your own screen.
Checkpoint: selecting a template should open that design in the editor. You can personalize it there before returning the image to your host.
Step 7 — Receive the published PNG
The launch helper defines one export option. Its outer fields identify the UI action, and PublishAction defines the returned data:
idreturnImagelabelUse imagestyleButtonStyle()targetpublishpublishFileTypeimage/pngoutputTypeAssetDataType.BASE64closeTargetOnExporttrueenableByDefaulttrueSelect Use image in the editor. The onPublish callback receives the intent and PublishParams. The payload contains a nullable asset list; it can also include exportButtonId and documentId. The export event is not itself a Bitmap.
Use the public selection helper inside onPublish to find a nonempty payload:
import com.adobe.express.embedsdk.AssetDataType
import com.adobe.express.embedsdk.OutputAsset
import com.adobe.express.embedsdk.PublishParams
// Use inside onPublish; do not remove the editor from this callback.
fun selectPublishedImage(params: PublishParams): OutputAsset? {
val assets = params.asset.orEmpty()
return assets.firstOrNull { it.dataType == AssetDataType.BASE64 && !it.getData().isNullOrBlank() }
?: assets.firstOrNull { it.dataType == AssetDataType.URL && !it.getData().isNullOrBlank() }
}
- Mark the publish event as received in your host state.
- Pass its
PublishParamstoselectPublishedImage. - If it returns
null, report that the event contains no supported image. - Read the selected asset's
getData()value and use itsdataTypeto choose the host decoder input.
The helper prefers a nonempty Base64 asset and then a nonempty URL-typed asset. The sample's callback similarly prefers Base64 over URL and reports missing or blank image data. These helpers select data; they don't validate the image or determine whether a URL is safe to load.
For this flow, route BASE64 data to the decoder's Base64 input. A URL asset is acceptable to the sample decoder only when its data is a local content:// URI. The SDK's URL data type doesn't mean that every returned value is local, and the host decoder doesn't fetch HTTP or HTTPS images.
Step 8 — Decode and display the image in your host
Keep image handling in the host rather than embedding it in SDK navigation. The integration sample separates the payload from decoding with an ExportImageInput value, whose type is either BASE64 or LOCAL_URI, and passes that value to ExportImageDecoder.
Follow its bounded PNG path:
- Launch the host display operation in
lifecycleScope. - Run decoding with
withContext(Dispatchers.IO)so Base64 conversion, content-stream reading, and bitmap work don't block view updates. - For Base64 input, accept raw encoded data or take the suffix after the literal
base64,marker used in a data URI. - For local URI input, require the
contentscheme and open it withContentResolver. Reject remote URLs instead of silently adding a network fetch. - Check the byte limit and PNG signature before bitmap decoding.
- Read image bounds first, validate the dimensions, and choose a power-of-two sample size before decoding the bitmap.
- Return to the main-thread coroutine to call
ImageView.setImageBitmap, set a meaningful content description, and show Image received.
The sample applies these limits; they are host safeguards, not SDK export guarantees:
content:// only; reject HTTP and HTTPSThe selected asset can therefore still fail to display: its payload might be malformed, exceed a host limit, refer to unavailable local content, or fail bitmap decoding. Show a host-side message such as Returned image could not be displayed; try publishing again. Preserve coroutine cancellation by rethrowing CancellationException rather than converting it into an image error.
You can start decoding when the publish callback arrives even though the editor still covers the host screen. The image becomes visible when the workflow returns to that screen; decoding and screen dismissal don't have to happen in the same callback.
Step 9 — Finish the session without racing navigation
Handle the end of the workflow in this order:
- In
onPublish, consume the data and start decoding. Don't pop the browser or remove the editor there. - Let the SDK process the requested editor close.
- In
onSessionFinished, use the host's publish-received state to request dismissal of the browse surface. - In the app configuration's
onDismisshandler, pop the host-mounted browser's tagged back-stack entry when FragmentManager state isn't saved.
onPublish precedes SDK teardown. onSessionFinished is a session/cleanup notification, not unconditional proof that every editor Fragment was successfully removed. Keep the AppCompat host and your own navigation state consistent rather than treating that notification as a universal cleanup guarantee.
The browse context's close() also delegates to the host's onDismiss handler. Your host must implement that handler; a close request doesn't independently remove the Fragment you mounted.
The launch helper guards both launch and dismiss transactions with isStateSaved. Its dismiss branch skips the pop if state is saved and doesn't implement a deferred retry. For a host that can move to the background during the flow, record pending dismissal and apply it when the Activity can safely transact again. Make that host action idempotent so a repeated notification doesn't pop unrelated navigation entries.
data-slots=text
data-variant=success
ImageView with Image received. in the status view. Cancellation or invalid image data should produce a status message instead, not a fabricated success image.Resolve common integration problems
Use the failing boundary to decide what to inspect:
ExpressEmbedSdk or Wishes configuration types don't resolveWEB_VIEW_NOT_AVAILABLESDK_INITIALIZATION_IN_PROGRESSUNSUPPORTED_APIeditDesign call.onError details and confirm the provisioned environment and application configuration with your Adobe partner contact. An AccessDenied response alone doesn't establish its cause.getData() values before decoding.content:// data only and doesn't support remote-image fetching.onSessionFinished handling, onDismiss back-stack tag, and pending-dismiss behavior when state is saved. Don't infer successful UI removal solely from the session notification.When escalating a service failure, provide the SDK version, development environment, failing step, and sanitized callback error details through your approved partner support channel. Don't send Maven passwords, bearer tokens, or exported image payloads as diagnostic logs, and don't substitute another app's client identity to work around an unexplained failure.
What you integrated
You now have the integration boundaries for one complete Android flow:
- Your app consumes the provisioned SDK Maven dependency and retains one initialized interface.
- Your AppCompat Activity mounts the native Wishes and Greetings browser.
- The SDK opens the selected template in its editor using the same workflow configuration.
- Your publish callback selects the returned PNG data, and your bounded decoder prepares it for the host
ImageView. - Your session and dismiss handlers return to the host without removing the editor from
onPublish.
Keep the three public Kotlin helpers separate from your host's UI, callback object, and decoder implementation. They demonstrate SDK calls; the surrounding host wiring owns the complete integration.
For further lookup, use the Android SDK API reference for public classes, configuration, and callbacks, and the Android SDK release list for general release information. Confirm prerelease-to-release module availability with your partner contact before changing the version used by this guide.