Supported on: iOS SDK
1.0.61+ · Android SDK 1.0.33+ · Flutter SDK 1.0.3+Supported Formats
Lottie Animations
Lottie animations are rendered natively on both platforms. On iOS the SDK useslottie-ios (added as a separate SPM dependency in your iOS host project), and on Android Lottie Compose is bundled directly into the AppDNA 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 using the native Rive runtime on each platform. iOS requires addingrive-ios as a separate SPM dependency in your iOS host project. The Android Rive runtime is bundled with the AppDNA Android 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 uses native players on each platform — AVPlayer on iOS, ExoPlayer on Android. Flutter sees a fully rendered native view, so playback performance is identical to a hand-written native implementation.- 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
Images
Remote images are fetched and cached by the native SDKs:- Remote URL — standard JPEG, PNG, and WebP supported on both platforms
- GIF — animated GIFs are supported in in-app message hero areas and push notification rich content
- SVG — supported on both platforms with native rasterization
- Corner radius, sizing, content mode — configured in the Console
Asset URL Conventions
Each rich-media block in the AppDNA Console uses a typed URL field:
🔴 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 one is always safe.
Any of these fields can also accept inline data where supported (e.g. Lottie supports inline JSON). The SDK auto-detects the format from the URL extension or the inline payload.
Embedding Rich Media in Paywalls
Rich media inside paywalls is configured per section. The console exposes section types likepaywall_header, paywall_features, and paywall_cta, each of which can host an image, Lottie animation, Rive animation, or video. The Flutter SDK simply presents the paywall and the native renderer handles the rest:
- Hero Lottie — attach a Lottie URL to the
paywall_headersection to play a looping product animation while the user reads features. - Feature illustrations — inside
paywall_features, each feature row can carry an inline icon (Lucide / SF Symbols / Material / emoji) or a remote image. - Video proof — a short looping product demo can be embedded into a section with
paywall_videostyle. - CTA pulse — the
paywall_ctasection supports CTA animations (pulse, glow, bounce) configured entirely from the Console.
Embedding Rich Media in Onboarding
Onboarding steps are composed of an ordered list of content blocks. Each block has a type and optional styling. Supported block types include:
Blocks are added in the Console — the Flutter SDK simply presents the flow:
Embedding Rich Media in In-App Messages
In-app messages support a hero area at the top of the modal, banner, or fullscreen layout. The hero can host:- An image (with optional GIF playback)
- A Lottie animation
- A short looping video
AppDNAInAppMessageDelegate:
Animation Playback Control
All animation playback is configured per section / per content block in the AppDNA Console. The native renderers honor the data fields embedded in the config:
🔴 The Console writes the
video_*-prefixed names for the video fields —
video_autoplay, video_loop, video_muted, video_controls — and those are the ones to
look for in a published document. iOS also decodes the unprefixed autoplay / loop /
muted / controls as a legacy fallback; Android reads the prefixed form only — so prefer the
prefixed names whenever you inspect or hand-write a document.
These fields are wired automatically by the renderer — you do not need to forward them through any Dart bridge.
Local Asset Embedding
scripts/bundle-assets.ts in the AppDNA repo walks a downloaded config bundle, fetches the assets
it references into an appdna-assets/ folder, and rewrites each URL to the relative path
appdna-assets/<hash><ext>. Ship that folder inside your app — the iOS app bundle, or app/src/main/assets/ on Android —
and the SDK loads each file from disk instead of the network, falling through to the remote URL
when the file is not there.
What it does not cover:
- A path the build did not bundle falls through to the remote URL, on both platforms: the file is checked before the path is rewritten, so a partially bundled config degrades to the network rather than to broken images.
- Only images resolve — the lookup ends in a
UIImage, so a rewritten.json,.lottie,.riv,.mp4or.movpath resolves on no platform. A config containing those should not be bundled: the remote URL that worked before bundling is replaced by a relative path that does not.
assets declared in pubspec.yaml are not visible to the native renderers, so put bundled rich media in the iOS / Android native locations described above.
Fallback Behavior
The native renderers degrade gracefully when an asset cannot be loaded:- Lottie / Rive — if the file is missing, malformed, or the network request fails, the block is skipped (no empty space, no crash). The next block in the layout takes its slot.
- Video — if the video URL is unreachable, the optional
thumbnail_urlis shown instead. If no thumbnail is configured, the block is skipped. - Image — if the image URL is unreachable, the block is skipped. There is no broken-image placeholder.
- Inline JSON Lottie — if the inline JSON is malformed, the block is skipped and a debug log is emitted.
Performance Notes
- Lazy loading — rich-media blocks are loaded on demand. A Lottie / Rive / video asset is only fetched when its block enters the visible viewport.
- Asset caching — remote assets are cached by the native HTTP stack (URLCache on iOS, OkHttp disk cache on Android). Subsequent presentations reuse the cached file.
- Memory — the native renderers tear down decoded animations when the host view is dismissed, so animations do not accumulate memory across flows.
- Cold start — avoid placing large video files on the very first onboarding step. Use a Lottie or static image for the welcome step and put video on later steps so playback starts after configuration is fully loaded.
- Inline JSON — inline Lottie payloads add to the config bundle size. Prefer remote URLs for animations larger than ~50 KB to keep config refreshes fast.
Icons
The SDK supports structured icon references across all modules. Icons resolve to platform-native equivalents:
Icons are used in onboarding content blocks, paywall feature lists, in-app message CTA buttons, and survey question options.
Visual Effects
Visual effects (haptics, particle effects, blur backdrops) are rendered by the native SDKs and configured in the Console. The Flutter SDK does not need any wiring — effects fire automatically based on the triggers attached to the section or step.Haptic Feedback
Haptic feedback is triggered automatically based on Console configuration. 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. 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 use the native platform’s high-performance blur primitive (iOSUIVisualEffectView, Android RenderEffect) for performant glassmorphism effects:
- Radius — blur intensity
- Tint — overlay color tint
- Saturation — color saturation adjustment
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 native SDKs use platform-native fonts only. Custom web fonts are not supported. Specify system font families in the Console and the SDK will render them using each platform’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.

