Troubleshooting
Common SDK integration issues across platforms — and the fixes.
If your symptom isn't here, check the per-guide troubleshooting tables — each guide (Push notifications, Identify users, Track events, Deep linking) has a "Common pitfalls" section covering issues specific to that area.
Push notifications
No sound plays even though I configured a custom channel sound
Android locks notification channel attributes (sound URI, importance, vibration) at first creation on API 26+. If your app created the channel before you bound the custom sound, subsequent updates to the channel won't change it.
Fix:
- For testing: install on a fresh state (
adb shell pm clear <package>) — confirms the SDK's intended channel state. - For an existing install: create a new channel with a different ID and
direct future pushes to it. Optionally delete the old channel via
Swan.deleteNotificationChannel(oldId).
Sound on iOS is set per-notification in the campaign payload, not bound to a channel. If the sound file isn't bundled in your app target or the filename in the payload doesn't match exactly, the OS falls back to the default sound.
Check:
- The sound file is in your app bundle (drag it into Xcode → "Add to target" your main app).
- The campaign payload's
soundvalue matches the filename exactly, including extension (e.g."chime.caf").
Deep link opens the app but doesn't route to the target screen on cold-start
Cold-start delivers the launching intent via Activity.getIntent() in
onCreate, not via onNewIntent. Make sure your MainActivity calls
Swan.handleNotificationIntent(intent) in onCreate before your own
routing logic:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Swan.addDeepLinkOpenedListener { payload ->
payload.route?.let { routeTo(it) }
}
Swan.handleNotificationIntent(intent) // ← before any other routing
// ... rest of onCreate
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
Swan.handleNotificationIntent(intent) // warm-start
}Cold-start on iOS is delivered through UNUserNotificationCenter's
delegate. Set your AppDelegate (or a dedicated delegate object) as
UNUserNotificationCenter.current().delegate, then in
userNotificationCenter(_:didReceive:withCompletionHandler:) forward the
payload to the SDK:
Swan.shared.handleNotificationUserInfo(
response.notification.request.content.userInfo
)Full snippet in Getting started → iOS.
You probably forgot to register messaging().onNotificationOpenedApp(...)
at module scope in index.js. This handler captures both warm-start taps
and iOS carousel taps. Without it, push taps that route through Firebase
(rather than Notifee) silently drop:
// index.js — module scope, NOT inside a component
import messaging from '@react-native-firebase/messaging';
import { createNotificationOpenedHandler } from '@loyalytics/swan-react-native-sdk';
messaging().onNotificationOpenedApp(createNotificationOpenedHandler());POST_NOTIFICATIONS denied on Android 13+
The SDK does not crash when the runtime permission is denied:
Swan.hasNotificationPermission()returnsfalseSwan.isPushEnabled()returnsfalse- Queued events still flush normally
- The OS silently drops notification UI
Host-app responsibility is to call Swan.requestNotificationPermission()
at an appropriate moment (after a meaningful user action, not at cold
launch — see the
Push notifications guide).
Carousel notification truncates images
Android 13's RemoteViews enforces a ~2 MB parcel size limit. Carousel
templates with many large images can hit it, leading to dropped frames.
Fix: Update to the latest SDK version — the Android SDK auto-scales carousel images by API level and item count to stay under the limit.
Identity
Swan.identify() returns "Credential not found"
Device registration hasn't completed yet. Either:
-
Wait for the device-registered listener to fire, then call
identify:Swan.addDeviceRegisteredListener { Swan.identify("user-12345") }import SwanSDK from '@loyalytics/swan-react-native-sdk'; const sdk = SwanSDK.getCurrentInstance(); sdk?.addListener('deviceRegistered', () => { sdk?.identify('user-12345'); }); -
Or just re-call
identifylater — events are queued in the meantime under the anonymous identifier and will merge into the customer profile when identify eventually succeeds.
In production this is rare (device registration completes in <2 s after first launch). In dev with backend outages, you'll see this until the backend recovers.
identify() looks like it worked but events aren't reaching the right profile
The CDID might have been swapped, but events queued before the
identifier change aren't auto-rewritten. Call Swan.flush() before
logout() / before switching profiles via login() — login() does
this for you automatically.
Build
Lint analysis crashes with IncompatibleClassChangeError
Known AGP 8.7 ↔ androidx-lifecycle 2.10+ incompatibility —
NonNullableMutableLiveDataDetector crashes. Pin
androidx-lifecycle 2.8.x in your host app until AGP 9 is available, or
disable that specific lint check in lint.xml.
google-services.json is missing when building
Required for the Firebase Messaging plugin to wire up FCM. Download
google-services.json from your Firebase project and place it in your
app module. Add the com.google.gms.google-services plugin to your
module-level build.gradle.kts.
The SDK itself doesn't require google-services.json — only your host
app does, for Firebase.
Reporting an issue
If you hit a bug not covered here, contact your Swan support contact with:
- SDK version + package coordinate (e.g.
cx.swan:swan-android-sdk:1.0.0) - Minimum repro (platform version, host-app dependency excerpts, log
snippet showing the
[SwanSDK] *markers) - Expected vs actual behavior