Displaying Flows
Control how and when flows appear in your app.
Displaying Flows
Flows can appear in your app automatically based on trigger conditions, or you can show them manually by ID. This page explains both approaches and the lifecycle callbacks you can listen for.
Automatic display
By default, the SDK forwards user signals to the Setgreet API whenever the user state changes -- after an identifyUser call, an event track, or a screen track. The server evaluates trigger conditions for the current user and, if a flow matches, returns it in the response. The SDK then displays the returned flow automatically.
This is the recommended approach for most use cases. You configure targeting in the dashboard, and the SDK handles the rest.
How trigger evaluation works
- The SDK sends a signal (identify, track event, or screen view) to the Setgreet API.
- The server checks all published flows to see if any trigger conditions match the current user state.
- If a match is found, the server returns the flow in its response.
- If multiple flows match, the server returns the one with the highest priority (set in the dashboard).
- The server also decides every Condition node in that flow for the current user, from their stored attributes and segments, and removes the other path from the flow's navigation, so the device follows the chosen one. The flow's other screens are still sent to the device, so a Condition node decides where users go but does not keep the other path's content off the device.
- The SDK renders the returned flow on the device.
Trigger evaluation is server-side, so the SDK requires a network connection to show flows. Offline caching is not supported today.
Manual display
If you want to show a specific flow at a specific moment in your app's code, use the showFlow method with the flow's ID:
// iOS
Setgreet.shared.showFlow(flowId: "YOUR_FLOW_ID")// Android
Setgreet.showFlow("YOUR_FLOW_ID")// React Native
import { showFlow } from '@setgreet/react-native-sdk';
showFlow('YOUR_FLOW_ID');// Flutter
await Setgreet.showFlow("YOUR_FLOW_ID");Manual display ignores the flow's trigger conditions -- the flow is shown immediately regardless of user attributes, events, or segments.
Condition nodes inside the flow still apply: when showFlow fetches the flow, the server decides them from the attributes and segments stored for the current user. Call identifyUser before showFlow so those attributes are up to date (see User Identification).
You can find the flow ID in the dashboard by opening a flow: it is the last part of the URL,
app.setgreet.com/flows/<flow-id>. Flow IDs are 24-character hexadecimal strings.
Flow callbacks
Register callbacks to listen for flow lifecycle events. This lets your app respond to what happens during a flow -- for example, navigating to a screen after the flow closes, or logging a custom event.
// iOS
Setgreet.shared.setFlowCallbacks { callbacks in
callbacks
.onFlowStarted { event in
print("Flow started: \(event.flowId)")
}
.onFlowCompleted { event in
print("Flow completed: \(event.flowId)")
}
.onFlowDismissed { event in
print("Flow dismissed: \(event.flowId), reason: \(event.reason)")
}
.onActionTriggered { event in
print("Action \(event.actionType) on flow \(event.flowId)")
}
.onCustomAction { event in
print("Custom action on flow \(event.flowId): \(event.data ?? [:])")
}
}// Android
Setgreet.setFlowCallbacks {
onFlowStarted { event ->
println("Flow started: ${event.flowId}")
}
onFlowCompleted { event ->
println("Flow completed: ${event.flowId}")
}
onFlowDismissed { event ->
println("Flow dismissed: ${event.flowId}, reason: ${event.reason}")
}
onActionTriggered { event ->
println("Action ${event.actionType} on flow ${event.flowId}")
}
onCustomAction { event ->
println("Custom action on flow ${event.flowId}: ${event.data}")
}
}Available lifecycle callbacks
| Callback | Description |
|---|---|
onFlowStarted | The flow began and the first screen appeared. |
onFlowCompleted | The user reached the flow's final screen. |
onFlowDismissed | The flow closed without being completed. |
onScreenChanged | The user navigated to another screen within the flow. |
onActionTriggered | A button action was triggered, just before it runs: next, URL, share, a permission request, a purchase and the rest. actionType names it. |
onPermissionRequested | A permission request action fired (notifications, location, camera, tracking, microphone, photo library). |
onTrackEvent | An in-flow Track Event action fired. |
onCustomAction | A custom action defined in the dashboard fired. |
onError | A flow the SDK set out to show could not be shown: the request failed, the response could not be read, or the flow could not be presented. |
The outcome of a paywall's purchase and restore buttons has its own callbacks, described in Purchases and paywalls.
Dismiss reasons
When onFlowDismissed fires, the event includes a reason explaining why the flow closed:
| Reason | Description |
|---|---|
userClose | The user tapped the close button, or tapped the dimmed area outside a bottom sheet or popup. |
userSkip | The user tapped a skip button. |
backPress | The user pressed the hardware or system back button (Android). |
swipeDown | The user swiped a bottom sheet flow down. |
replaced | The flow was replaced by another flow with higher priority. |
programmatic | The flow was closed by app code. |
completed | The flow reached its end and closed naturally. |
Action types
Buttons and interactive components can trigger these actions. onActionTriggered reports each one as its actionType, the same value on every SDK (on Android, the ActionType name in lower case):
| Action | actionType | Description |
|---|---|---|
next | next | Navigate to the next screen in the flow. |
previous | previous | Navigate to the previous screen. |
skip | skip | Skip the current step and move on. |
dismiss | dismiss | Close the flow. |
url | url | Open an external URL in the system browser. |
requestNotificationPermission | request_notification_permission | Trigger the notification permission prompt. |
requestLocationPermission | request_location_permission | Trigger the location permission prompt. |
requestCameraPermission | request_camera_permission | Trigger the camera permission prompt. |
requestTrackingPermission | request_tracking_permission | Trigger the App Tracking Transparency prompt (iOS only). |
requestMicrophonePermission | request_microphone_permission | Trigger the microphone permission prompt. |
requestPhotoLibraryPermission | request_photo_library_permission | Trigger the photo library permission prompt. |
requestReview | request_review | Present the native "rate this app" prompt. |
share | share | Open the native share sheet. |
openSettings | open_settings | Open the device settings page for the app. |
trackEvent | track_event | Fire an event through the SDK's tracking pipeline. actionName is the event name. |
custom | custom | A custom action handled by your app via onCustomAction. |
purchase | purchase | Buy a product through your purchase provider. |
restorePurchases | restore_purchases | Restore purchases through your purchase provider. |
Use these action callbacks to integrate flow outcomes with your app's navigation, state management, or business logic.
Permission actions
The SDK declares no permissions of its own. A permission prompt appears only when a flow uses one of the request actions and your app has made the platform declaration below. When the declaration is missing, the SDK logs a message, reports the permission as not required, and the flow moves on. It never crashes.
| Action | iOS Info.plist key | Android <uses-permission> |
|---|---|---|
requestNotificationPermission | none | POST_NOTIFICATIONS (Android 13 and later; granted implicitly before) |
requestLocationPermission | NSLocationWhenInUseUsageDescription | ACCESS_FINE_LOCATION |
requestCameraPermission | NSCameraUsageDescription | CAMERA |
requestTrackingPermission | NSUserTrackingUsageDescription | none; Android has no equivalent, so the result is not required and the flow advances |
requestMicrophonePermission | NSMicrophoneUsageDescription | RECORD_AUDIO |
requestPhotoLibraryPermission | NSPhotoLibraryUsageDescription | READ_MEDIA_IMAGES (Android 13 and later) or READ_EXTERNAL_STORAGE before |
Every request fires onPermissionRequested with the permission type and a result: granted, denied, permanently_denied, already_granted, or not_required. On iOS, limited photo library access counts as granted. The flow advances after any result; a primer screen's job is to explain the request, not to block on it.
Apple accepts a screen that explains why tracking is requested before the App Tracking Transparency prompt. Apple's App Store Review Guidelines (5.1.2) prohibit offering a reward for allowing tracking or locking features behind it. Keep the copy explanatory.
A screen can also skip itself when the permission it asks for is already granted. See Skip when already granted.