Embedded widgets
An embed is a Flyy surface inside your own layout: a milestone strip on your home screen, an offer card in checkout. Embeds have a transparent background, no chrome, and size themselves to their content.
| Widget | What it shows |
|---|---|
TRAIL | Progress trail across the user's active campaigns |
MILESTONES | All milestone programs |
MILESTONE | The user's current milestone, compact |
OFFER | Offer cards; or one offer with loadOffer(id) |
Any full screen can be embedded too.
Views and UIKit
- Android
- iOS
val embed = FlyyEmbedView(context).apply {
onCta = FlyyEmbedView.OnCtaListener { target, _ ->
Flyy.open(context, deepPath = target) // you route taps
}
onState = FlyyEmbedStateListener { state ->
section.isVisible = state == "ready" // hide empty sections
}
load(FlyyWidget.TRAIL)
}
container.addView(embed)
// when the screen is gone for good:
embed.release()
For a page-sized embed that scrolls inside itself, set fillHeight = true and
load a screen: embed.load(FlyyScreen.REWARDS).
let card = FlyyEmbedView()
card.onCta = { target, _ in Flyy.open(from: self, deepPath: target) }
card.onState = { state in section.isHidden = (state == .empty || state == .error) }
card.load(.milestone)
stackView.addArrangedSubview(card)
Height comes from intrinsicContentSize, so it works in stack views and Auto Layout with no height constraint.
Compose and SwiftUI
- Android
- iOS
Needs com.theflyy:flyy-compose.
FlyyEmbed(
widget = FlyyWidget.MILESTONE,
onStatus = { if (it.isBlank) sectionVisible = false },
onCta = { route -> nav.push(route) },
placeholder = { status -> MyShimmer(status) },
)
// a full screen filling a tab
FlyyEmbed(FlyyScreen.REWARDS, modifier = Modifier.weight(1f))
FlyyEmbed releases the WebView on dispose and shows your placeholder until
the content is ready, so loading and empty states stay in your design.
Navigation inside your own chrome. A tap can open another Flyy surface under your own top bar instead of the full-screen SDK:
val nav = rememberFlyyEmbedNavigator()
val route = nav.current
if (route != null) {
MyBackHeader(route.title, onBack = { nav.pop() })
FlyyEmbed(route, onCta = nav::push, modifier = Modifier.weight(1f))
} else {
MyHomeTabs(onRewards = { nav.push(FlyyScreen.REWARDS) })
}
struct HomeView: View {
@State private var showTrail = true
var body: some View {
VStack {
if showTrail {
FlyyEmbed(.trail,
onState: { if $0 == .empty || $0 == .error { showTrail = false } },
onCta: { target, _ in Flyy.open(path: target) })
}
FlyyEmbedPage(.rewards) // a full screen inside a tab
}
}
}
Embed states
| State | Meaning | What to do |
|---|---|---|
loading | Content is on its way | Show a placeholder |
ready | Content painted | Show the section |
empty | Nothing to show for this user | Hide the section, title included |
error | Couldn't load | Hide the section or show a retry |
:::caution Memory Each embed is a WebView (30–80 MB). Keep one or two per screen and never one per list row. Release embeds you no longer need. :::