Testing & debugging
When a survey does not appear, the SDK always knows why. Two places tell you: the debug log and the surveyNotShown callback.
Debug log
Section titled “Debug log”Set the log level to debug when you initialize the SDK.
Responsly.shared.initialize(sdkKey: "rsk_…", options: ResponslyOptions(logLevel: .debug))Responsly.initialize(applicationContext, "rsk_…", ResponslyOptions(logLevel = LogLevel.DEBUG))Responsly.initialize('rsk_…', { logLevel: 'debug' })await Responsly.initialize('rsk_…', options: const ResponslyOptions(logLevel: ResponslyLogLevel.debug));ResponslySDK.Initialize("rsk_…", new ResponslyOptions { LogLevel = ResponslyLogLevel.Debug });Every line starts with [Responsly]. At the debug level the SDK logs:
- every targeting decision, with the Survey ID and the reason when a survey is not shown,
- every configuration download and its result,
- every step in the life of a survey: loading, shown, answered, completed, closed.
| Level | Logs |
|---|---|
none | Nothing. |
error | Problems that need your attention, such as an invalid SDK key. |
info | Errors, plus notable events such as initialization and failed downloads. |
debug | Everything above, plus every decision. |
Where to read it:
| App | Where |
|---|---|
| iOS | Xcode console, or Console.app with the filter [Responsly] |
| Android | Logcat with the filter Responsly |
| React Native, Flutter | The same native logs: Xcode console for iOS, Logcat for Android |
| Unity on a device | The same native logs: Xcode console for iOS, adb logcat for Android |
| Unity Editor | The Unity Console. In the Editor every call is a no-op that only logs. |
Use debug in development builds and error or none in release builds. The installation pages show how to switch on the build type.
On iOS, the debug level also makes the survey inspectable with Safari’s Web Inspector.
See the reason in the app
Section titled “See the reason in the app”In development builds it helps to surface the reason on screen or in your own log.
final class NotShownLogger: ResponslyListener { func surveyNotShown(_ event: ResponslySurveyNotShownEvent) { print("Survey \(event.surveyId) not shown: \(event.reason.rawValue)") }}
let notShownToken = Responsly.shared.addListener(NotShownLogger())Responsly.addListener(object : ResponslyListener { override fun onSurveyNotShown(event: SurveyNotShownEvent) { Log.d("Survey", "Survey ${event.surveyId} not shown: ${event.reason.value}") }})if (__DEV__) { Responsly.addListener({ onSurveyNotShown: ({ surveyId, reason }) => console.log(`Survey ${surveyId} not shown: ${reason}`), })}Responsly.events.listen((event) { if (event case SurveyNotShown(:final surveyId, :final reason)) { debugPrint('Survey $surveyId not shown: $reason'); }});ResponslySDK.SurveyNotShown += e => Debug.Log($"Survey {e.SurveyId} not shown: {e.Reason}");Not-shown reasons
Section titled “Not-shown reasons”The checks run in a fixed order and the first failing one gives the reason. The order is in Targeting.
| Reason | What it means | How to fix it |
|---|---|---|
disabled | The SDK is switched off with setTrackingEnabled(false). The setting survives restarts. | Call setTrackingEnabled(true). |
not_initialized | showSurvey was called before initialize. | Call initialize at app start, before any showSurvey. |
config_unavailable | The configuration could not be downloaded and there is no cached copy. | Check the SDK key (it starts with rsk_ and belongs to the right team) and the network connection. With an invalid key nothing is shown until the app is restarted with a valid one. |
survey_not_found | The Survey ID is not in the configuration. | Check the ID. Check that the Mobile app channel is switched on for that form and that the form belongs to the team of the SDK key. If you just switched it on, the device may still hold the old configuration; see Configuration cache. |
another_survey_visible | A survey is on screen, or one is waiting for its delay. | Expected: one survey at a time. Close the current one, or space the triggers apart. |
another_survey_selected | Several surveys matched the same trigger and another one was shown. | Expected. If the wrong one wins, separate them with audience or frequency rules. |
paused | Your code called pause. | Call resume, or use showSurvey, which ignores the pause. |
outside_schedule | The current time is before from or at or after until. | Adjust the schedule. Check the device clock. |
platform_mismatch | The survey is limited to the other platform. | Add this platform in the audience settings, or clear the platform list. |
app_mismatch | The app ID is not in the survey’s list. The comparison is case-sensitive. | Add the app ID. Debug builds often have a different ID, for example with a .debug suffix. |
locale_mismatch | The SDK’s language is not in the survey’s languages list. | Add the language, clear the list, or check what you pass to setLocale. |
audience_mismatch | The identified / not identified rule or the attribute conditions are not met. | Check that identify or setAttributes ran before the trigger, that attribute names are spelled the same, and that values have the type the condition expects (number 5, not text "five"). |
already_completed | This install has completed the survey and the frequency is once or until completed. | Expected. To test again, call reset or use force. |
frequency_limit | The survey was already shown and the frequency is once, or the maximum number of displays is reached. | Expected. Change the frequency, raise the maximum, or call reset while testing. |
recontact_wait | The days between displays have not passed yet. | Wait, lower the number of days, or call reset while testing. |
global_cooldown | The team-wide pause between surveys is still running after another survey. | Wait, shorten the pause, or use showSurvey, which ignores it. |
sampled_out | This install is outside the share of users. | Set the share to 100% while testing. The answer for an install does not change until you raise the share or reinstall. |
screen_changed | The survey had a delay and the user left the screen before it ended. | Shorten the delay, or trigger on an event instead of a screen. |
vetoed | Your display guard returned false. | Check the guard’s condition. |
load_failed | The survey did not load within 15 seconds, or the load failed. | Check the connection. A survey needs the network when it is shown. Nothing is counted as a display. |
survey_unavailable | The survey loaded but reported that it cannot be answered right now. | Open the form’s link in a browser to see what respondents see, and check that the form is accepting responses. |
Force a survey
Section titled “Force a survey”To see a survey regardless of audience, schedule and frequency, call showSurvey with force. It also skips the display guard.
Responsly.shared.showSurvey("AbCd1234", options: ResponslyShowOptions(force: true))Responsly.showSurvey("AbCd1234", ShowOptions(force = true))Responsly.showSurvey('AbCd1234', { force: true })Responsly.showSurvey('AbCd1234', force: true);ResponslySDK.ShowSurvey("AbCd1234", new ShowOptions { Force = true });A forced survey can still fail with disabled, not_initialized, config_unavailable, survey_not_found, another_survey_visible, load_failed or survey_unavailable. If it appears, the integration works and the cause is in the targeting rules.
A debug menu entry that forces each of your surveys is a cheap way to let designers and QA review them in the real app.
Test targeting
Section titled “Test targeting”Targeting state lives on the device, so most surprises come from history left by earlier test runs.
| To test | Do this |
|---|---|
| A rule from scratch | Call reset. It clears event counts, display history and identity, and downloads the configuration again. |
| Frequency rules | Set every time while you test other things, then set the real mode and call reset once. |
| ”From occurrence no.” | Counts survive restarts. Call reset to start from zero. |
| Share of users | Test at 100%. An install’s position is fixed; only a reinstall gives a new one. |
| Audience conditions | Call identify or setAttributes with the test values before the trigger. Watch for audience_mismatch. |
| The app launch trigger | Quit the app completely and start it again. Returning from the background is not a launch. |
| A delay on a screen trigger | Stay on the screen for the whole delay. Leaving gives screen_changed. |
| Languages | Call setLocale instead of changing the device language. Call setLocale with null afterwards. |
| The pause between surveys | It applies to triggers only. Show one survey by a trigger, then fire another trigger. |
Test displays and test responses are real: they are counted in the Mobile app tab and saved in the form’s results. Delete the test responses afterwards, or test with a copy of the form.
Configuration cache
Section titled “Configuration cache”The SDK does not ask the server on every call. It works from a cached copy of the configuration.
- The cache lives for about 10 minutes.
- At
initializethe SDK loads the cached copy and downloads a new one if there is none or it has expired. - When the app returns to the foreground with an expired cache, it downloads again.
- While the cache is fresh, restarting the app does not download anything.
A change saved in the Mobile app tab therefore reaches a device within about 10 minutes, at the next app start or return to the foreground after the cache expires.
To pick up a change immediately while testing, call reset or reinstall the app. Both drop the cache.
Other cases:
- Offline with a cache. Targeting still runs, but a survey cannot load and reports
load_failed. - Offline without a cache (first start).
trackandscreencalls wait in a short queue and are evaluated when the configuration arrives.showSurveyreportsconfig_unavailable. - Download failed. The SDK keeps the cached copy and tries again the next time the app comes to the foreground.
- Invalid SDK key. The SDK drops the cache and shows nothing until the next app start.
Check the numbers
Section titled “Check the numbers”The Mobile app tab shows how many times the survey was shown, completed and closed in the last 30 days. The SDK sends these counts in small batches, so the numbers can lag a little behind what you just did on the device. Sending the app to the background flushes them. Nothing is counted while the SDK is switched off with setTrackingEnabled(false).