Skip to main content
Supported on: iOS SDK 1.0.61+ · Android SDK 1.0.33+ · Flutter SDK 1.0.3+
The AppDNA Flutter SDK renders rich media — Lottie animations, Rive animations, video, and images — through native platform pipelines on each side: SwiftUI + CoreAnimation on iOS, Jetpack Compose + Rive’s Android renderer on Android. Animations and effects look and feel identical to anything you would build by hand. All rich media content is configured in the AppDNA Console, no Dart code changes required.

Supported Formats

Lottie Animations

Lottie animations are rendered natively on both platforms. On iOS the SDK uses lottie-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.
Lottie is supported in onboarding content blocks, paywall sections, in-app message hero areas, and survey thank-you screens.

Rive Animations

Rive animations are rendered using the native Rive runtime on each platform. iOS requires adding rive-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
Video is supported in onboarding content blocks, paywall sections, and in-app message hero areas.

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 like paywall_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:
Common patterns:
  • Hero Lottie — attach a Lottie URL to the paywall_header section 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_video style.
  • CTA pulse — the paywall_cta section 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
The rest of the message body uses the same content block system as onboarding, so you can mix icons, badges, headings, text, and CTAs underneath the hero. In-app messages are triggered by event rules in the Console, but you can also gate display with an 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

Images only, and there is no asset:// URL form — an asset:// URL is treated as an ordinary URL, fails to fetch, and the block draws nothing. The scheme that exists is the relative appdna-assets/ path below, and it works on both platforms.
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, .mp4 or .mov path 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.
For a genuinely offline-first flow, keep the assets remote and rely on the HTTP caches described under Performance Notes. Flutter 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_url is 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.
All fallbacks are silent in release builds. In debug builds, the native SDKs emit warnings you can see in Xcode / Logcat.

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 (iOS UIVisualEffectView, Android RenderEffect) for performant glassmorphism effects:
  • Radius — blur intensity
  • Tint — overlay color tint
  • Saturation — color saturation adjustment
Blur is supported as a backdrop for in-app message modals and fullscreen overlays.

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.

No Dart Code Required

All rich media features are configured in the AppDNA Console. The Flutter SDK forwards a configuration ID to the native renderer, which handles all decoding, layout, and animation. You do not need to write any Dart code to use Lottie, Rive, video, haptics, particles, blur, icons, or content blocks.