Unwrap Surveys Mobile SDK
Last updated: March 2, 2026
Version 1.0 | iOS 15+ | Android API 26+
The Unwrap Surveys SDK lets you embed conversational survey flows directly into your iOS and Android apps. Present surveys at the right moment, customize the look and feel, and let Unwrap's AI generate intelligent follow-up questions in real time.
Overview
The SDK provides three core capabilities:
Survey Rendering -- displays survey flows configured in the Unwrap dashboard
Manual Presentation -- launch surveys programmatically at the right moment in your app
Theming & Customization -- match the survey UI to your app's design system
Survey content (questions, screen order, scoring type, AI follow-up behavior) is configured entirely in the Unwrap dashboard. The SDK pulls that config at runtime, so you can update what gets asked without shipping a new app version. The SDK is responsible for rendering, styling, presenting, and collecting responses.
How It Works
Create and configure a survey in the Unwrap dashboard -- choose question types, set prompts, configure AI follow-ups, and define the screen flow.
The dashboard gives you a survey UUID (e.g.
srv_onboarding_nps).Initialize the SDK with that UUID. The SDK downloads the survey configuration at app launch.
Call
Unwrap.presentSurvey()wherever you want the survey to appear, and optionally attach metadata at config time or submission time.
Installation
iOS (Swift Package Manager)
Note: The Unwrap Surveys SDK is distributed as a private package. Your team will receive repository access as part of onboarding. If you need access, contact your Unwrap account manager or email sdk-access@unwrap.ai.
Add the Unwrap Surveys package to your project:
https://github.com/unwrap-nlp/unwrap-surveys-ios.git
Or add it to your Package.swift:
dependencies: [
.package(url: "https://github.com/unwrap-nlp/unwrap-surveys-ios.git", from: "1.0.0")
]
CocoaPods
First, register the private Unwrap spec repo:
pod repo add unwrap https://github.com/unwrap-nlp/specs.git
Then add the pod to your Podfile:
source 'https://github.com/unwrap-nlp/specs.git'
source 'https://cdn.cocoapods.org/' # Keep the default CDN source for other pods
pod 'UnwrapSurveys', '~> 1.0'
Android (Gradle)
Add the Unwrap maven repository and dependency to your app-level build.gradle. Authentication is required -- your credentials are provided during onboarding.
First, add your Unwrap maven credentials to your project's gradle.properties file (or ~/.gradle/gradle.properties for global config). Do not commit these to source control.
# ~/.gradle/gradle.properties
unwrapMavenUser=your_username_here
unwrapMavenToken=your_token_here
Then add the repository and dependency in your app-level build.gradle:
repositories {
maven {
url 'https://maven.unwrap.ai/releases'
credentials {
username = project.findProperty("unwrapMavenUser") ?: ""
password = project.findProperty("unwrapMavenToken") ?: ""
}
}
}
dependencies {
implementation 'ai.unwrap:surveys-sdk:1.0.0'
}
ProGuard/R8: The SDK bundles its own consumer ProGuard rules in the AAR, so no additional keep rules are needed for release builds.
Initialization
Initialize the SDK at app launch with your survey UUID. The SDK downloads the survey configuration from Unwrap and caches it locally. You can optionally pass metadata as free-form key-value pairs that will be attached to all responses from this session.
iOS
Use a @UIApplicationDelegateAdaptor to ensure the SDK is configured before any views render:
import SwiftUI
import UnwrapSurveys
class AppDelegate: NSObject, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
Unwrap.configure(
surveyId: "srv_onboarding_nps"
)
return true
}
}
@main
struct MyApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
With optional metadata:
Unwrap.configure(
surveyId: "srv_onboarding_nps",
metadata: [
"userId": "user_8291",
"plan": "enterprise",
"company": "Acme Corp",
"appVersion": "3.2.1"
]
)
Android
import ai.unwrap.surveys.Unwrap
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
Unwrap.configure(
context = this,
surveyId = "srv_onboarding_nps"
)
}
}
With optional metadata:
Unwrap.configure(
context = this,
surveyId = "srv_onboarding_nps",
metadata = mapOf(
"userId" to "user_8291",
"plan" to "enterprise",
"company" to "Acme Corp",
"appVersion" to "3.2.1"
)
)
Metadata passed at config time is attached to every survey response in this session. Use it to associate responses with users, accounts, app versions, or any context that helps you analyze feedback in the Unwrap dashboard.
Survey Flows
Survey flows are configured entirely in the Unwrap dashboard. A typical flow looks like this:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Numeric Rating │────▶│ Open-Ended │────▶│ AI Follow-Up │
│ (Score Screen) │ │ (Why Screen) │ │ (Detail Screen) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
In the dashboard, you configure the question types, prompts, scoring scales, AI follow-up behavior, and screen order. The dashboard generates a survey UUID that you pass to Unwrap.configure().
The SDK's role is to fetch that configuration at runtime, render the screens, collect responses, and send them back to Unwrap. You control how the survey looks (theming), how it's presented (modal, bottom sheet, inline), and when it appears in your app.
Presenting Surveys
Call Unwrap.presentSurvey() to show the survey. You control where in your app this happens -- after a purchase, at the end of onboarding, when a support ticket is resolved, or anywhere else that makes sense.
You can optionally pass submission-time metadata that will be attached to this specific response in addition to any config-time metadata.
iOS
// Present the survey
Unwrap.presentSurvey { result in
switch result {
case .completed(let response):
print("Score: \(response.score ?? -1)")
print("Feedback: \(response.openEndedText ?? "")")
case .dismissed:
print("User dismissed the survey")
case .error(let error):
print("Survey error: \(error)")
}
}
// Present with submission-time metadata
Unwrap.presentSurvey(
metadata: [
"screen": "order_confirmation",
"orderId": "ord_9381"
]
) { result in
// handle result
}
// Present from a specific view controller (UIKit only).
// Use this when you need to control which view controller presents the survey,
// e.g. in a UIKit-based app or a mixed UIKit/SwiftUI codebase.
Unwrap.presentSurvey(from: viewController)
Android
// Present the survey
Unwrap.presentSurvey { result ->
when (result) {
is SurveyResult.Completed -> {
Log.d("Unwrap", "Score: ${result.response.score}")
Log.d("Unwrap", "Feedback: ${result.response.openEndedText}")
}
is SurveyResult.Dismissed -> {
Log.d("Unwrap", "User dismissed")
}
is SurveyResult.Error -> {
Log.e("Unwrap", "Error: ${result.error}")
}
}
}
// Present with submission-time metadata
Unwrap.presentSurvey(
metadata = mapOf(
"screen" to "order_confirmation",
"orderId" to "ord_9381"
)
) { result ->
// handle result
}
// Present from a specific Activity or Fragment
Unwrap.presentSurvey(activity = this)
Metadata Merging
When you pass metadata at both config time and submission time, submission-time values take precedence for duplicate keys.
// Config time
Unwrap.configure(
surveyId: "srv_post_purchase",
metadata: ["userId": "user_8291", "plan": "enterprise"]
)
// Submission time
Unwrap.presentSurvey(metadata: ["plan": "pro", "orderId": "ord_9381"]) { result in
// Final metadata on this response:
// "userId": "user_8291" (from config)
// "plan": "pro" (submission overrides config)
// "orderId": "ord_9381" (from submission)
}
Theming
Customize the survey UI to match your app's design system. Theming controls how dashboard-configured surveys are rendered -- colors, fonts, corner radii, presentation style, and animations.
iOS
let theme = UnwrapTheme(
primaryColor: UIColor(red: 108/255, green: 92/255, blue: 231/255, alpha: 1.0),
backgroundColor: .systemBackground,
textColor: .label,
cornerRadius: 16,
font: .systemFont(ofSize: 16, weight: .medium),
titleFont: .systemFont(ofSize: 20, weight: .bold),
buttonStyle: .filled,
presentationStyle: .bottomSheet, // .bottomSheet, .fullScreen, .card, .inline
overlayOpacity: 0.4,
animation: .spring
)
Unwrap.setTheme(theme)
Android
val theme = UnwrapTheme(
primaryColor = Color.parseColor("#6C5CE7"),
backgroundColor = Color.WHITE,
textColor = Color.BLACK,
cornerRadius = 16.dp,
fontFamily = ResourcesCompat.getFont(context, R.font.inter),
titleFontSize = 20.sp,
bodyFontSize = 16.sp,
buttonStyle = ButtonStyle.FILLED,
presentationStyle = PresentationStyle.BOTTOM_SHEET,
overlayOpacity = 0.4f,
animation = AnimationType.SPRING
)
Unwrap.setTheme(theme)
Presentation Styles
StyleDescription | |
| Slides up from the bottom. Default. |
| Full-screen modal with navigation bar. |
| Centered card with backdrop overlay. |
| Embeds directly in your view hierarchy (no modal). Used automatically by |
Delegate / Listener Callbacks
Listen to survey lifecycle events for analytics or custom behavior.
iOS
class SurveyHandler: UnwrapSurveyDelegate {
func surveyWillPresent(surveyId: String) {
// Pause background tasks, analytics, etc.
}
func surveyDidPresent(surveyId: String) {
analytics.track("survey_shown", properties: ["surveyId": surveyId])
}
func surveyStepCompleted(surveyId: String, step: SurveyStep, data: StepData) {
if case .score(let value) = data {
print("User scored: \(value)")
}
}
func surveyCompleted(surveyId: String, response: SurveyResponse) {
print("Survey \(surveyId) completed at \(response.completedAt)")
if let score = response.score {
print("Score: \(score)")
}
}
func surveyDismissed(surveyId: String, atStep: SurveyStep) {
analytics.track("survey_dismissed", properties: [
"surveyId": surveyId,
"dismissedAt": atStep.name
])
}
}
Unwrap.setDelegate(SurveyHandler())
Android
Unwrap.setSurveyListener(object : UnwrapSurveyListener {
override fun onSurveyWillPresent(surveyId: String) {
// Pause background tasks
}
override fun onSurveyPresented(surveyId: String) {
analytics.track("survey_shown", mapOf("surveyId" to surveyId))
}
override fun onStepCompleted(surveyId: String, step: SurveyStep, data: StepData) {
when (data) {
is StepData.Score -> Log.d("Unwrap", "Score: ${data.value}")
is StepData.Text -> Log.d("Unwrap", "Text: ${data.value}")
is StepData.AIFollowUp -> Log.d("Unwrap", "Follow-up: ${data.value}")
}
}
override fun onSurveyCompleted(surveyId: String, response: SurveyResponse) {
Log.d("Unwrap", "Survey $surveyId completed at ${response.completedAt}")
}
override fun onSurveyDismissed(surveyId: String, atStep: SurveyStep) {
analytics.track("survey_dismissed", mapOf(
"surveyId" to surveyId,
"dismissedAt" to atStep.name
))
}
})
Offline Support
The SDK caches the survey configuration locally and queues responses when the device is offline. Responses are synced automatically when connectivity is restored. No additional setup is required.
Queued responses are persisted to disk and survive app restarts. The SDK retries with exponential backoff when connectivity returns. Queued responses expire after 7 days to avoid submitting stale data.
// Check pending response count (iOS)
let pending = Unwrap.pendingResponseCount
print("\(pending) responses waiting to sync")
// Check pending response count (Android)
val pending = Unwrap.pendingResponseCount
Log.d("Unwrap", "$pending responses waiting to sync")
Jetpack Compose & SwiftUI Support
The UnwrapSurveyView component renders a survey inline within your view hierarchy, without presenting a modal. It automatically uses the .inline presentation style regardless of your theme's presentationStyle setting.
SwiftUI
import SwiftUI
import UnwrapSurveys
struct FeedbackView: View {
var body: some View {
VStack {
// The survey renders all content (questions, prompts, follow-ups)
// as configured in the Unwrap dashboard
UnwrapSurveyView()
.frame(height: 300)
}
}
}
Jetpack Compose
import ai.unwrap.surveys.compose.UnwrapSurveyView
@Composable
fun FeedbackScreen() {
Column {
// The survey renders all content (questions, prompts, follow-ups)
// as configured in the Unwrap dashboard
UnwrapSurveyView(
modifier = Modifier
.fillMaxWidth()
.height(300.dp)
)
}
}
Testing
Use the preview API to test surveys without affecting production data. Preview mode shows the survey immediately.
// iOS -- Force show the survey
Unwrap.preview()
// Android -- Force show the survey
Unwrap.preview()
Debug Console
Enable verbose logging to see config downloads and API calls:
Unwrap.enableDebugLogging(true)
Sample output:
[Unwrap] Config downloaded for srv_post_purchase
[Unwrap] Presenting survey: srv_post_purchase
[Unwrap] Step completed: score (value: 8)
[Unwrap] Step completed: why (value: "Love the new export feature")
[Unwrap] AI follow-up generated
[Unwrap] Step completed: ai_follow_up_1 (value: "Mostly use it for weekly reports")
[Unwrap] Survey completed -- submitting response
[Unwrap] Response submitted successfully
Data & Privacy
All survey responses are transmitted over TLS 1.3 and stored encrypted at rest. The SDK collects only the data you explicitly pass via metadata. No device fingerprinting, no background data collection.
Note on offline queueing: When the device is offline, responses are queued locally on-device and synced when connectivity returns. This queueing happens only in response to explicit user actions (completing a survey). No data is collected or transmitted in the background outside of this sync.
For GDPR and CCPA compliance, you can disable the SDK for users who opt out of data collection:
Unwrap.setDataCollectionEnabled(false) // Disables all surveys
Unwrap.setDataCollectionEnabled(false)
Full Integration Example
import SwiftUI
import UnwrapSurveys
class AppDelegate: NSObject, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
// 1. Initialize with survey UUID and session metadata
Unwrap.configure(
surveyId: "srv_post_purchase",
metadata: [
"userId": "user_8291",
"plan": "enterprise",
"company": "Acme Corp"
]
)
// 2. Theme (controls how the survey renders)
Unwrap.setTheme(UnwrapTheme(
primaryColor: UIColor(red: 108/255, green: 92/255, blue: 231/255, alpha: 1.0),
presentationStyle: .bottomSheet
))
return true
}
}
@main
struct MyApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
Button("Complete Order") {
completeOrder()
// 3. Present the survey with submission-time metadata
Unwrap.presentSurvey(metadata: [
"orderId": "ord_9381",
"orderValue": 84.50
]) { result in
if case .completed(let response) = result {
print("Score: \(response.score ?? -1)")
}
}
}
}
}
SDK Versioning
Survey configurations created in the Unwrap dashboard include a schema version. The SDK validates configs against its supported schema version at download time. If the dashboard produces a config for a newer schema than your installed SDK supports, the SDK will fall back to the last compatible cached config and log a warning in debug mode. Keep the SDK updated to access new screen types and configuration options.
Support
Dashboard: app.unwrap.ai
API Reference: docs.unwrap.ai/sdk
Email: support@unwrap.ai