How to Use Device Mode Integrations
11 minute read
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 standard device mode integrations using the integration APIs
- Use your own destination instance with a device mode integration
- Add custom plugins to integrations
Add a standard device mode integration
To add a standard integration, instantiate the integration plugin and add it to your Analytics instance.
Android (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)import com.rudderstack.sdk.kotlin.android.plugins.devicemode.IntegrationPlugin;
import com.rudderstack.integration.kotlin.firebase.FirebaseIntegration;
// Get reference to your integration plugin
IntegrationPlugin integrationPlugin = new FirebaseIntegration();
// Add integration plugin to your analytics instance
analytics.add(integrationPlugin);iOS (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)@import RudderStackAnalytics;
@import RudderIntegrationFirebase;
// Get reference to your integration plugin
RSSFirebaseIntegration *integration = [RSSFirebaseIntegration new];
// Add integration plugin to your analytics instance
[self.analytics addPlugin: integration];Always add theintegrationPluginto theAnalyticsinstance before invoking any APIs on theintegrationPlugin. 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:
| Integration | Android (Kotlin) | iOS (Swift) |
|---|---|---|
| Braze | v2.1.0 and above | v3.0.0 and above |
| Adjust | v2.1.0 and above | v2.0.0 and above |
| CleverTap | v2.1.0 and above | v2.0.0 and above |
| Sprig | v2.1.0 and above | v2.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
- Add the destination SDK to your app. The integration page gives the dependency or package, and the supported versions.
- Initialize the destination SDK in your app.
- Pass the instance to the integration, and add the integration to the SDK.
The following example uses Braze:
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)))import BrazeKit
import RudderIntegrationBraze
import RudderStackAnalytics
// Initialize Braze with your own configuration
let configuration = Braze.Configuration(apiKey: "<BRAZE_API_KEY>", endpoint: "<BRAZE_ENDPOINT>")
let braze = Braze(configuration: configuration)
// Pass your Braze instance to the integration
analytics.add(plugin: BrazeIntegration(braze: braze))How the integration behaves
- The integration waits for the source configuration before it sends events.
getDestinationInstance()returns your instance.onDestinationReady()passes your instance when the destination is ready.
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 initializationgetDestinationInstance(): 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 theonDestinationReady()API for critical startup logic that requires the destination to be ready before proceeding.
Android (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}")
}
}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;
IntegrationPlugin integrationPlugin = new FirebaseIntegration();
analytics.add(integrationPlugin);
integrationPlugin.onDestinationReady((instance, result) -> {
if (result instanceof Result.Success) {
// Destination is ready
System.out.println("Destination is ready: " + instance);
} else if (result instanceof Result.Failure) {
// Handle initialization failure
Exception exception = ((Result.Failure) result).getException();
System.out.println("Destination initialization failed: " + exception.getMessage());
}
});iOS (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)")
}
}@import RudderStackAnalytics;
@import RudderIntegrationFirebase;
RSSFirebaseIntegration *integration = [RSSFirebaseIntegration new];
[self.analytics addPlugin: integration];
[self.analytics onDestinationReadyForKey:integration.key :^(id _Nullable destination, NSError * _Nullable error) {
if (error == nil) {
// Destination is ready, safe to send events
[RSSLoggerAnalytics debug:[NSString stringWithFormat:@"Destination is ready: %@", destination]];
} else {
// Handle initialization failure
[RSSLoggerAnalytics debug:[NSString stringWithFormat:@"Destination initialization failed: %@", [error localizedDescription]]];
}
}];Example: Fetch Firebase app instance ID
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)
}
}
}
}import FirebaseAnalytics
integrationPlugin.onDestinationReady { instance, result in
switch result {
case .success:
if let firebaseAnalytics = instance as? Analytics {
firebaseAnalytics.appInstanceID { appInstanceID in
if let appInstanceID = appInstanceID {
// Send to your backend for analytics correlation
sendToBackend(appInstanceID)
}
}
}
case .failure(_):
break
}
}getDestinationInstance()
getDestinationInstance() lets you directly access the destination SDK instance for advanced use cases like interacting with destination-specific APIs or features.
Use thegetDestinationInstance()API for on-demand access later in the app lifecycle when you need to interact with the destination instance immediately.
Android (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
}Object destination = integrationPlugin.getDestinationInstance();
if (destination != null) {
// Interact with the destination SDK
System.out.println("Destination instance: " + destination);
// Cast to specific destination type if needed
// FirebaseAnalytics firebaseDestination = (FirebaseAnalytics) destination;
}iOS (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
}id destination = [integration getDestinationInstance];
if (destination != nil) {
// Interact with the destination SDK
[RSSLoggerAnalytics debug:[NSString stringWithFormat:@"Destination instance: %@", destination]];
// Cast to specific destination type if needed
// Analytics *firebaseDestination = [integration getDestinationInstance];
}Example 1: Set default event parameters for Firebase
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)
}import FirebaseAnalytics
if let firebaseAnalytics = firebaseIntegration.getDestinationInstance() as? Analytics {
// Set the global parameters
let parameters: [String: Any] = [
"app_variant": "premium",
"global_source": "mobile_app"
]
// This ensures these parameters are included in every subsequent Firebase event
Analytics.setDefaultEventParameters(parameters)
}Example 2: Use Braze content cards
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.")
}import RudderIntegrationBraze
import BrazeKit
if let braze = brazeIntegration.getDestinationInstance() as? Braze {
// Manually request a refresh of Content Cards
braze.contentCards.requestRefresh()
// Access current content cards for custom UI rendering
let unreadCount = braze.contentCards.cards.filter { !$0.viewed }.count
print("User has \(unreadCount) unread Content Cards.")
} else {
print("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)
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)import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.rudderstack.sdk.kotlin.core.Analytics;
import com.rudderstack.sdk.kotlin.core.internals.models.Event;
import com.rudderstack.sdk.kotlin.core.internals.models.TrackEvent;
import com.rudderstack.sdk.kotlin.core.internals.plugins.Plugin;
import kotlin.coroutines.Continuation;
// Step 1: Create a custom plugin
public class MyCustomPlugin implements Plugin {
private Analytics analytics;
private static final Plugin.PluginType pluginType = Plugin.PluginType.PreProcess;
@Override
public void setAnalytics(@NonNull Analytics analytics) {
this.analytics = analytics;
}
@Override
public void setup(@NonNull Analytics analytics) {
this.analytics = analytics;
}
@Nullable
@Override
public Object intercept(@NonNull Event event, @NonNull Continuation<? super Event> $completion) {
// Filter out debug events
if (event instanceof TrackEvent && ((TrackEvent) event).getEvent().startsWith("Debug_")) {
return null; // This will drop the event
}
return event; // Pass through all other events
}
@NonNull
@Override
public Analytics getAnalytics() {
return this.analytics;
}
@NonNull
@Override
public Plugin.PluginType getPluginType() {
return pluginType;
}
@Override
public void teardown() {
// Clean up resources if needed
}
}
// Step 2: Add integration to analytics
IntegrationPlugin integrationPlugin = new FirebaseIntegration();
analytics.add(integrationPlugin);
// Step 3: Add custom plugin to integration
MyCustomPlugin customPlugin = new MyCustomPlugin();
integrationPlugin.add(customPlugin);iOS (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)// Step 1: Create custom plugin
// MyCustomPlugin.h
#import <Foundation/Foundation.h>
@import RudderStackAnalytics;
@interface MyCustomPlugin : NSObject <RSSEventPlugin>
@end
// MyCustomPlugin.m
@implementation MyCustomPlugin
@synthesize pluginType;
- (instancetype)init {
self = [super init];
if (self) {
}
return self;
}
- (enum RSSPluginType)pluginType {
return RSSPluginTypeTerminal;
}
- (RSSEvent *)intercept:(RSSEvent *)event {
if ([event isKindOfClass:[RSSTrackEvent class]]) {
RSSTrackEvent *trackEvent = (RSSTrackEvent *)event;
NSString *eventName = trackEvent.eventName;
if ([eventName hasPrefix:@"Debug_"]) {
return nil; // This will drop the event
}
}
return event; // Pass through all other events
}
@end
// Step 2: Add integration to analytics:
// Import Firebase integration at the top of the file
@import RudderIntegrationFirebase;
// Adding firebase integration to analytics instance
RSSFirebaseIntegration *integrationPlugin = [RSSFirebaseIntegration new];
[self.analytics addPlugin: integrationPlugin];
// Step 3: Add custom plugin to integration:
MyCustomPlugin *customPlugin = [MyCustomPlugin new];
[self.analytics addPlugin:customPlugin destinationKey:integrationPlugin.key];Troubleshooting
| Issue | Solution |
|---|---|
| Integration not initializing |
|
| Destination instance is null |
|
| Events not reaching destination |
|
See more
- Device Mode Integration APIs: Complete API reference for device mode integration APIs
- How to Build Custom Device Mode Integrations: Create custom integrations for unsupported destinations
- Plugin Architecture: Learn about the plugin system available in the mobile SDKs