Supported on: Android SDK
1.0.33+Supported Formats
Lottie Animations
Lottie animations are rendered natively using Lottie Compose, which is bundled with the SDK.An animation comes from a URL, and its colours are baked into the file. There is no inline
animation payload and no colour remapping: both were declared in the config models for a while,
neither renderer ever read them, and they were removed rather than implemented. A remote
.json or
.lottie keeps the config a device downloads small, and a recolour belongs in the file the designer
ships, where it can be seen before it is published.Rive Animations
Rive animations are rendered natively using the Rive Android runtime, bundled with the SDK.
Rive is supported in onboarding content blocks, paywall sections, in-app messages (both modal and
fullscreen) and server-driven screens — everywhere Lottie is except surveys.
Video
Inline video playback with ExoPlayer:- Remote URL — provide a URL to an MP4 or HLS video
- Thumbnail — optional poster image shown before playback
- Controls — autoplay, loop, muted, show/hide controls
- Sizing — configurable height and corner radius
Visual Effects
Haptic Feedback
Haptic feedback uses the AndroidHapticFeedbackConstants and VibrationEffect APIs. Supported haptic types:
🔴 Nothing fires unless
haptic.enabled is true. The config object gates every trigger, so a
trigger set on its own does nothing — triggerIfEnabled returns early when the config is absent or
not enabled, on both platforms. A type the SDK does not recognise is also ignored rather than
falling back to a default.
The triggers, each taking one of the types above:
Particle Effects
Particle effects overlay the current screen using Jetpack Compose Canvas. Supported effect types:
🔴 The surface decides when the trigger’s moment is, and each surface has only the moments it
can actually hold. An effect whose trigger is not one of its surface’s moments has nothing to fire
in, and nothing is drawn.
🔴
on_flow_complete is not an onboarding moment. The host takes the flow off the screen as
soon as it completes, so an effect armed there would be dismissed in the frame it started. A
celebration at the end of a flow belongs on whatever screen the app shows next.
Fixed in iOS 1.0.84 / Android 1.0.56. Before those versions only
on_appear ever fired: the
overlay re-checked the authored trigger after its parent had already decided to show it, and
refused anything else. A paywall effect left on its Android default — on_purchase — therefore
never ran at all.Blur / Glassmorphism
Blur backdrops useRenderEffect.createBlurEffect() (API 31+) with graceful fallback on older devices:
- Radius — blur intensity
- Tint — overlay color tint
- Saturation — color saturation adjustment
Asset URL Conventions
Each rich-media block carries its own typed URL field. The SDK picks the renderer from the block’stype, not from the file extension, so the field name is what matters:
🔴 A screen SECTION spells the poster
thumbnail_url, without the video_ prefix, because the
screens editor writes it that way. Content blocks and paywall sections use the prefixed name. Both
renderers accept either spelling on either surface, so the prefixed form is always safe.
Embedding Rich Media in Paywalls
Rich media inside a paywall belongs to a section. A section can host an image, a Lottie or Rive animation, or a video, and the SDK draws it when the paywall is presented — there is nothing to forward from your code:- Hero animation — a looping Lottie at the top of the paywall while the user reads the features.
- Feature illustrations — each feature row can carry a Material icon, an emoji or a remote image.
- Video proof — a short looping product demo in its own section.
- CTA animation — pulse, glow and bounce are configured on the CTA section itself.
Embedding Rich Media in Onboarding
An onboarding step is an ordered list of content blocks. The rich-media types areimage,
video, lottie and rive; they sit alongside heading, text, button, list, badge,
icon, toggle, divider and spacer in the same list and are laid out in order.
Embedding Rich Media in In-App Messages
A modal, banner or fullscreen message has a hero area at the top, which can host an image (animated GIFs included), a Lottie animation or a short looping video. The body below the hero uses the same content-block system as onboarding. Messages are triggered by event rules in the Console. ImplementAppDNAInAppMessageDelegate to
observe them, or to suppress one:
Animation Playback Control
Playback is authored per block in the Console; the renderer reads the fields off the published document and there is nothing to forward through your own code.
🔴 Video fields are
video_-prefixed and Lottie’s are not. autoplay and loop belong to
Lottie; the video block’s equivalents are video_autoplay and video_loop. And Lottie’s speed is
lottie_speed, not speed — a bare speed is a different field entirely (the particle
background’s slow / normal / fast token), so authoring speed on a Lottie block does nothing.
Android reads the prefixed video names only. iOS also decodes the unprefixed autoplay / loop /
muted / controls for the video block as a legacy fallback, so a document written against iOS
alone can carry names Android ignores — prefer the prefixed names.
Local Asset Embedding
scripts/bundle-assets.ts in the AppDNA repo walks a downloaded config bundle, fetches the images
it references into an appdna-assets/ folder, and rewrites each URL to the relative path
appdna-assets/<hash><ext>. Put that folder in app/src/main/assets/ and the SDK’s image loader
resolves each path out of the app’s own assets instead of the network.
🔴 A path the build did not bundle falls through to the remote URL. The asset is opened before
the path is rewritten, so a partially bundled config degrades to the network rather than to broken
images — and a config you have not bundled at all is unaffected.
What it does not cover: images only. The resolver loads an image, so a rewritten .json,
.lottie, .riv, .mp4 or .mov path resolves to nothing on any platform, and a config
containing those should not be bundled — the remote URL that worked before bundling would be
replaced by a relative path that does not. There is no asset:// URL form.
Performance Notes
- Lazy loading — a rich-media block starts loading when its composable first enters composition, so an asset on a later onboarding step is not fetched while the user is on the first.
- Image caching — images load through a Coil
ImageLoaderthe SDK configures with a 50 MB disk cache and a memory cache of 25% of the app’s memory budget. - Memory — players and animations are released when the composable leaves composition, so they do not accumulate across flows.
- Cold start — avoid a large video on the very first onboarding step; use an image there and put video on a later step so playback starts after the config has settled.
- File size — the config a device downloads carries the URL, not the animation, so a large
.lottiecosts a fetch rather than a bigger config. Keep it under a few hundred KB anyway: it is fetched on the step that draws it.
Icons
The SDK supports structured icon references across all modules. Icons are resolved to platform-appropriate rendering:
Icons are used in onboarding content blocks, paywall feature lists, in-app message CTA buttons, and survey question options.
Content Blocks
Onboarding steps support rich content blocks that are rendered in order using Jetpack Compose. Each block has a type and optional styling:Styling
All visual elements support fine-grained styling through the Console:- Text styles — font family, size, weight, color, alignment, line height, letter spacing, opacity
- Backgrounds — solid color, linear/radial gradient, or remote image with overlay
- Borders — width, color, style, and per-corner radius
- Shadows — offset, blur, spread, and color
- Spacing — per-side padding and margin
- Animations — entry animations (slide, fade, scale), section stagger, CTA pulse/glow/bounce, dismiss animations
The SDK uses platform-native fonts only. Custom web fonts are not supported. Specify system font families in the Console, and the SDK will render them using the device’s default typeface.
Dynamic Content Templates
Rich media content supports template interpolation using{{variable}} syntax. Available namespaces:
Use the pipe syntax for fallback values:
{{user.name | there}} renders as “there” if the user name is not set or was identified as null.

