Skip to content

Callbacks

The SDK reports what happens to a survey through six callbacks. All of them are optional and all are delivered on the main (UI) thread.

CallbackFires whenFields
surveyShownThe survey became visible.survey (id, name), trigger (type, name)
surveyNotShownA survey matched a trigger, or was requested with showSurvey, but was not shown.surveyId, reason
questionAnsweredThe respondent answered a question.surveyId, questionId, type, title, value
surveyCompletedThe respondent submitted the survey.surveyId, responseId, answers
surveyDismissedThe survey was closed before it was completed.surveyId
surveyErrorThe survey reported an error while it was visible.surveyId, code, message

Implement ResponslyListener. Every method has a default implementation, so write only the ones you need.

import ResponslySDK
final class SurveyEvents: ResponslyListener {
func surveyShown(_ event: ResponslySurveyShownEvent) {
print("shown", event.survey.id, event.survey.name, event.trigger.type.rawValue)
}
func surveyNotShown(_ event: ResponslySurveyNotShownEvent) {
print("not shown", event.surveyId, event.reason.rawValue)
}
func questionAnswered(_ event: ResponslyQuestionAnsweredEvent) {
print("answered", event.questionId, event.type, event.title, event.value)
}
func surveyCompleted(_ event: ResponslySurveyCompletedEvent) {
print("completed", event.responseId, event.answers.count)
}
func surveyDismissed(_ event: ResponslySurveyDismissedEvent) {
print("dismissed", event.surveyId)
}
func surveyError(_ event: ResponslySurveyErrorEvent) {
print("error", event.code, event.message)
}
}
let token = Responsly.shared.addListener(SurveyEvents())
// later
Responsly.shared.removeListener(token)

The SDK keeps the listener until you remove it with the token.

ResponslyListenerBlock is the closure-based variant. Pass only the closures you need:

let token = Responsly.shared.addListener(ResponslyListenerBlock(
surveyCompleted: { event in print("completed", event.responseId) },
surveyDismissed: { event in print("dismissed", event.surveyId) }
))

reason is a ResponslyNotShownReason and trigger.type is a ResponslyTriggerType. Their rawValue is the string used throughout these docs, such as frequency_limit or app_launch. Every event has toDictionary(), which returns the fields with snake_case keys.

You can register several listeners. Each receives every event.

For one survey the callbacks arrive in this order:

  1. surveyShown, once.
  2. questionAnswered, zero or more times.
  3. Either surveyCompleted or surveyDismissed, once.

A survey that does not get on screen produces a single surveyNotShown instead. surveyError can arrive at any point between 1 and 3.

One exception: when closeSurvey cancels a survey that was still waiting to appear, surveyDismissed fires without a surveyShown before it. Write handlers that resume your app so that they also work when nothing was paused.

There is no callback for the moment a completed survey leaves the screen. After surveyCompleted the thank-you page stays until the auto-close time passes or the respondent closes it. If your code must continue only when the screen is clear, call closeSurvey in the surveyCompleted handler.

FieldDescription
survey.idThe Survey ID.
survey.nameThe form’s name in Responsly.
trigger.typeevent, screen, app_launch, or manual for showSurvey.
trigger.nameThe event or screen name. null (absent on iOS) for app_launch and manual.

Fires when the survey is actually visible, after it has loaded. This is the moment that counts as a display for frequency rules and for the shown count in the Mobile app tab. Pause video, audio or gameplay here.

FieldDescription
surveyIdThe Survey ID.
reasonOne of the not-shown reasons, for example frequency_limit or audience_mismatch.

Fires for every survey that matched a trigger or was requested with showSurvey and then failed a check. It is the programmatic twin of the debug log. Expect it often: with several active surveys, most trigger calls end here for most of them.

FieldDescription
surveyIdThe Survey ID.
questionIdThe question’s ID.
typeThe question’s type, for example nps, rating, multiple_choice, text.
titleThe question text as shown to the respondent.
valueThe answer. See Answer values.

Fires while the respondent is filling in the survey, before it is submitted. If the survey is dismissed afterwards, you have still received these events.

FieldDescription
surveyIdThe Survey ID.
responseIdThe ID of the saved response.
answersA list of answers, each with questionId, type, title, value.

Fires when the respondent submits the survey. From this moment the survey counts as completed for frequency rules.

FieldDescription
surveyIdThe Survey ID.

Fires when the survey goes away before completion: the respondent tapped the close button or the backdrop, pressed back on Android, or your code called closeSurvey. It does not fire when a completed survey closes.

FieldDescription
surveyIdThe Survey ID.
codeA short error code.
messageA description for logs. Do not show it to users.

Fires when something goes wrong in a survey that is already on screen. Problems before that point are reported as surveyNotShown with load_failed or survey_unavailable.

value is the answer as the survey stores it: a string, a number, a boolean, a list or a map, depending on the question type. Number scales such as NPS give a number.

PlatformHow value arrives
iOSA JSON-compatible value: String, NSNumber, array or dictionary. Cast it: (value as? NSNumber)?.intValue.
AndroidA JSON-compatible value: String, Number, Boolean, List or Map. Cast it: (value as? Number)?.toInt().
React NativeA plain JavaScript value.
FlutterA plain Dart value: String, num, bool, List or Map.
UnityJSON text in ValueJson, with the helpers AsString(), AsDouble() and AsBool().

Find a question by questionId when you need one specific question, or by type when the survey has a single question of that type.

Recipe: ask for a store review after NPS 9 or 10

Section titled “Recipe: ask for a store review after NPS 9 or 10”

Listen for surveyCompleted, find the NPS answer, and start the platform’s review flow for promoters.

surveyCompleted fires while the thank-you page is still visible. Close the survey first, then ask.

import StoreKit
import UIKit
import ResponslySDK
final class ReviewPrompt: ResponslyListener {
func surveyCompleted(_ event: ResponslySurveyCompletedEvent) {
guard
let nps = event.answers.first(where: { $0.type == "opinion_scale" }),
let score = (nps.value as? NSNumber)?.intValue,
score >= 9
else { return }
Responsly.shared.closeSurvey()
// Give the survey a moment to leave the screen.
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
guard let scene = UIApplication.shared.connectedScenes
.compactMap({ $0 as? UIWindowScene })
.first(where: { $0.activationState == .foregroundActive })
else { return }
if #available(iOS 16.0, *) {
AppStore.requestReview(in: scene)
} else {
SKStoreReviewController.requestReview(in: scene)
}
}
}
}
// At app start, after initialize:
let reviewPromptToken = Responsly.shared.addListener(ReviewPrompt())