How to Use Device Mode Integrations

Add and use device mode integrations in the Android (Kotlin) and iOS (Swift) SDKs.

This guide shows you how to add device mode integrations in your Android (Kotlin) and iOS (Swift) apps to send events directly to third-party destinations. It covers how to:

Add a standard device mode integration

To add a standard integration, instantiate the integration plugin and add it to your Analytics instance.

Android (Kotlin)

kotlin
import com.rudderstack.sdk.kotlin.android.plugins.devicemode.IntegrationPlugin
import com.rudderstack.integration.kotlin.firebase.FirebaseIntegration

// Get reference to your integration plugin
val integrationPlugin: IntegrationPlugin = FirebaseIntegration()

// Add integration plugin to your analytics instance
analytics.add(integrationPlugin)

iOS (Swift)

swift
import RudderStackAnalytics
import RudderIntegrationFirebase

// Get reference to your integration plugin
let integrationPlugin: IntegrationPlugin = FirebaseIntegration()

// Add integration plugin to your analytics instance
analytics.add(integrationPlugin)
Always add the integrationPlugin to the Analytics instance before invoking any APIs on the integrationPlugin. Calling them earlier may lead to unexpected behavior.

Use your own destination instance

By default, a device mode integration initializes the destination SDK from the destination configuration. Instead, you can also initialize the destination SDK in your app and pass that instance to the integration, which then only maps your events and sends them to your instance.

What you can do

With your own instance, your app sets up the destination SDK, and you can:

  • Set any destination SDK option in code, including the credentials. In the default setup, the integration sets up the destination SDK with only a few options from the destination configuration, for example the API key or the app token.
  • Start the destination SDK at app launch. In the default setup, the integration starts the destination SDK only after the SDK gets the source configuration.
  • Keep your existing destination SDK setup. If your app already sets up the destination SDK, you add the integration without a second setup.

The integration still maps your events and applies the mapping settings. It also applies the destination on/off switch, consent, and event filtering to the events that RudderStack sends. Your app does the destination-specific setup that the integration page describes, for example session tracking.

Supported integrations

These integrations take your instance:

IntegrationAndroid (Kotlin)iOS (Swift)
Brazev2.1.0 and abovev3.0.0 and above
Adjustv2.1.0 and abovev2.0.0 and above
CleverTapv2.1.0 and abovev2.0.0 and above
Sprigv2.1.0 and abovev2.0.0 and above

Note that:

  • The iOS (Swift) integrations in this table require the iOS (Swift) SDK v2.0.0 or above.
  • The AppsFlyer, Firebase, and Facebook App Events integrations don’t take an instance — your app always sets up these destination SDKs, and the integration uses that setup. See the relevant integration’s guide for the setup order.

Pass your instance

  1. Add the destination SDK to your app. The integration page gives the dependency or package, and the supported versions.
  2. Initialize the destination SDK in your app.
  3. Pass the instance to the integration, and add the integration to the SDK.

The following example uses Braze:

kotlin
import com.braze.Braze
import com.braze.BrazeActivityLifecycleCallbackListener
import com.braze.configuration.BrazeConfig
import com.rudderstack.integration.kotlin.braze.BrazeIntegration

// Initialize Braze with your own configuration
Braze.configure(
  this,
  BrazeConfig.Builder()
    .setApiKey("<BRAZE_API_KEY>")
    .setCustomEndpoint("<BRAZE_ENDPOINT>")
    .build()
)
registerActivityLifecycleCallbacks(BrazeActivityLifecycleCallbackListener())

// Pass your Braze instance to the integration
analytics.add(BrazeIntegration(Braze.getInstance(this)))

How the integration behaves

Important considerations

  • The connection settings that the integration uses to initialize the destination SDK still need a value in the destination configuration. The integration doesn’t use them with your instance. Each integration page names these settings.
  • Your app starts the destination SDK before the SDK gets the source configuration and checks consent. RudderStack consent applies only to the events that RudderStack sends. Your app decides what the destination SDK collects by itself.

Access destination instances

Device mode integrations provide the following APIs to interact with destination instances:

  • onDestinationReady(): Register a callback for destination initialization
  • getDestinationInstance(): Access the destination instance directly

See the Device Mode Integration APIs guide for the complete API specification.

onDestinationReady()

onDestinationReady() lets you perform actions only after the destination is successfully initialized. This is useful for fetching IDs or initializing resources that depend on the destination being ready.

Use the onDestinationReady() API for critical startup logic that requires the destination to be ready before proceeding.

Android (Kotlin)

kotlin
import com.rudderstack.sdk.kotlin.android.plugins.devicemode.IntegrationPlugin
import com.rudderstack.sdk.kotlin.core.internals.utils.Result
import com.rudderstack.integration.kotlin.firebase.FirebaseIntegration

val integrationPlugin: IntegrationPlugin = FirebaseIntegration()
analytics.add(integrationPlugin)

integrationPlugin.onDestinationReady { instance, result ->
    if (result is Result.Success) {
        // Destination is ready
        println("Destination is ready: $instance")
    } else {
        // Handle initialization failure
        println("Destination initialization failed: ${(result as Result.Failure).exception.message}")
    }
}

iOS (Swift)

swift
import RudderStackAnalytics
import RudderIntegrationFirebase

let integrationPlugin: IntegrationPlugin = FirebaseIntegration()
analytics.add(integrationPlugin)

integrationPlugin.onDestinationReady { instance, result in
    switch result {
    case .success:
        // Destination is ready, safe to send events
        print("Destination is ready: \(String(describing: instance))")
    case .failure(let error):
        // Handle initialization failure
        print("Destination initialization failed: \(error.localizedDescription)")
    }
}

Example: Fetch Firebase app instance ID

kotlin
import com.google.firebase.analytics.FirebaseAnalytics

integrationPlugin.onDestinationReady { instance, result ->
    if (result is Result.Success && instance is FirebaseAnalytics) {
        instance.appInstanceId.addOnSuccessListener { appInstanceId ->
            appInstanceId?.let {
                // Send to your backend for analytics correlation
                sendToBackend(it)
            }
        }
    }
}

getDestinationInstance()

getDestinationInstance() lets you directly access the destination SDK instance for advanced use cases like interacting with destination-specific APIs or features.

Use the getDestinationInstance() API for on-demand access later in the app lifecycle when you need to interact with the destination instance immediately.

Android (Kotlin)

kotlin
val destination = integrationPlugin.getDestinationInstance()
if (destination != null) {
    // Interact with the destination SDK
    println("Destination instance: $destination")
    // Cast to specific destination type if needed
    // val firebaseDestination = destination as? FirebaseAnalytics
}

iOS (Swift)

swift
if let destination = integrationPlugin.getDestinationInstance() {
    // Interact with the destination SDK
    print("Destination instance: \(destination)")
    // Cast to specific destination type if needed
    // let firebaseDestination = destination as? Analytics
}

Example 1: Set default event parameters for Firebase

kotlin
import android.os.Bundle
import com.google.firebase.analytics.FirebaseAnalytics

val firebaseAnalytics = firebaseIntegration.getDestinationInstance() as? FirebaseAnalytics

firebaseAnalytics?.let {
    val parameters = Bundle().apply {
        putString("app_variant", "premium")
        putString("global_source", "mobile_app")
    }
    // This ensures these parameters are included in every subsequent Firebase event
    it.setDefaultEventParameters(parameters)
}

Example 2: Use Braze content cards

kotlin
import com.braze.Braze

val braze = brazeIntegration.getDestinationInstance() as? Braze

braze?.let {
    // Manually request a refresh of Content Cards
    it.requestContentCardsRefresh()
    
    // Access current content cards for custom UI rendering
    val unreadCount = it.getContentCardCount()
    println("User has $unreadCount unread Content Cards.")
} ?: run {
    println("Braze integration is not yet ready or is disabled.")
}

Add custom plugins to integrations

Device mode integrations support adding custom plugins to modify or enhance event data before sending it to a specific destination. This enables filtering, transformation, or enrichment of events for specific destinations.

With this feature, you can:

  • Add different plugins to different device mode integrations (for example, Firebase, Amplitude, Braze)
  • Apply unique transformations, filtering, or enrichment logic for each destination
  • Handle destination-specific requirements without affecting other integrations

See the Plugin Architecture guide for more information on custom plugins.

Android (Kotlin)

kotlin
import com.rudderstack.sdk.kotlin.android.plugins.devicemode.IntegrationPlugin
import com.rudderstack.sdk.kotlin.core.internals.plugins.Plugin
import com.rudderstack.sdk.kotlin.core.internals.plugins.EventPlugin
import com.rudderstack.sdk.kotlin.core.internals.models.Event
import com.rudderstack.sdk.kotlin.core.internals.models.TrackEvent
import com.rudderstack.integration.kotlin.firebase.FirebaseIntegration

// Step 1: Create a custom plugin
class MyCustomPlugin : EventPlugin {
    override val pluginType = Plugin.PluginType.PreProcess
    
    override suspend fun intercept(event: Event): Event? {
        // Filter out debug events
        return if (event is TrackEvent && event.event.startsWith("Debug_")) {
            null // This will drop the event
        } else {
            event // Pass through all other events
        }
    }
}

// Step 2: Add integration to analytics
val integrationPlugin: IntegrationPlugin = FirebaseIntegration()
analytics.add(integrationPlugin)

// Step 3: Add custom plugin to integration
val customPlugin = MyCustomPlugin()
integrationPlugin.add(customPlugin)

iOS (Swift)

swift
import RudderStackAnalytics
import RudderIntegrationFirebase

// Step 1: Create a custom plugin
class MyCustomPlugin: EventPlugin {
    var pluginType: PluginType = .preProcess
    var analytics: Analytics?
    
    func intercept(event: any Event) -> (any Event)? {
        // Filter out debug events
        if let trackEvent = event as? TrackEvent,
           let eventName = trackEvent.event,
           eventName.hasPrefix("Debug_") {
            return nil // This will drop the event
        }
        return event // Pass through all other events
    }
}

// Step 2: Add integration to analytics
let integrationPlugin: IntegrationPlugin = FirebaseIntegration()
analytics.add(plugin: integrationPlugin)

// Step 3: Add custom plugin to integration
let customPlugin = MyCustomPlugin()
integrationPlugin.add(plugin: customPlugin)

Troubleshooting

IssueSolution
Integration not initializing
  • Ensure you have added the integration plugin to the Analytics instance before calling any integration APIs
  • Check that the destination is enabled in your RudderStack dashboard
  • Verify that required dependencies are properly installed
Destination instance is null
  • The destination may not be initialized yet. Use onDestinationReady() to wait for initialization
  • Check if the integration failed to initialize by handling the failure case in onDestinationReady()
  • Ensure the integration is properly configured in your RudderStack dashboard
Events not reaching destination
  • Verify the integration is added to your Analytics instance
  • Check that events are being tracked using the standard RudderStack APIs (track, identify, etc.)
  • Review destination-specific requirements in the destination’s documentation

See more

Questions? Let's figure it out together.

Join the RudderStack Slack community to connect with other users, customers, and the RudderStack team — or reach out for direct support.