Swan SDK
Guides

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 sound value matches the filename exactly, including extension (e.g. "chime.caf").

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() returns false
  • Swan.isPushEnabled() returns false
  • 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).

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:

  1. 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');
    });
  2. Or just re-call identify later — 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

On this page