Skip to main content

App SDK (iOS & Android)

Disco's native App SDK shows Disco offers inside your mobile app, on iOS and Android. This page covers both platforms: iOS first, then Android. If your app is built with React Native, use the React Native SDK instead. Current as of iOS SDK 1.0.10. For DiscoBeat channel partners: name the app as a surface at kickoff so your Disco team can set up keys and layouts early (see How your DiscoBeat launch works).

Both SDKs follow the same shape: initialize once at app launch with your ad-serving key, then call Disco.execute with session attributes when your surface is ready. DiscoBeat channel partners: offers serve per publisher, so use each publisher's own ad-serving key, created when Disco approves the publisher. _sandbox_ keys against staging, _live_ keys in production.

iOS​

Install​

In Xcode, go to File → Add Package Dependencies and enter the package URL:

https://github.com/co-op-commerce-inc/disco-ios-sdk

Choose Up to Next Major Version.

Initialize​

Initialize once at app launch, typically from application(_:didFinishLaunchingWithOptions:):

import UIKit
import DiscoSDK

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
Disco.initSdk(apiKey: "API_KEY")
return true
}
}

Your API key is your ad-serving key, provided by your Disco team.

Add an inline placement​

To add an inline placement using a Storyboard, add a new View to your View Controller:

Adding a View in the Xcode object library

Then, in the Identity Inspector, set the custom class of the View to DiscoEmbeddedView and the module to DiscoSDK:

Setting the custom class to DiscoEmbeddedView

Then define top, leading, and trailing constraints for the placement's width and position. For height, add a height constraint of zero. The DiscoEmbeddedView updates its own height constraint to match the size of the placement:

Adding constraints with a zero height

If you're not using a Storyboard, pass a programmatically created DiscoEmbeddedView instead.

Show offers​

Call Disco.execute when your surface is ready, for example when an order-status screen appears:

import UIKit
import DiscoSDK

class ViewController: UIViewController {
@IBOutlet weak var embeddedView: DiscoEmbeddedView!

override func viewDidLoad() {
super.viewDidLoad()

let attributes: [String: Any] = [
"user_details": [
"email": "shopper@example.com",
"first_name": "Jordan"
],
"placement_details": [
"view": "ORDER_STATUS", // e.g. THANK_YOU, ORDER_STATUS
"widget_id": "YOUR_WIDGET_ID" // from your Disco team (legacy layout_id also accepted)
]
]
Disco.execute(attributes: attributes, placement: .inline(embeddedView))
}
}

Everything after attributes and placement is optional. The full signature:

Disco.execute(
attributes: attributes,
placement: .inline(embeddedView), // or .overlay, .pullup
style: StyleOptions(appearance: .system),
onLoad: { /* widget rendered */ },
onError: { error in /* handle or log */ },
isSandbox: true // test requests don't affect reporting or attribution
)

Placements​

PlacementWhat renders
.inline(view)Offers render inside a DiscoEmbeddedView you place in your own layout
.overlayA fullscreen takeover
.pullupA bottom sheet

ConfigOptions controls dismissal behavior for the modal placements: dismissPullupOnBackdropTap and dismissOverlayOnBackdropTap (both default false).

Attributes​

attributes is a dictionary following the same request model as the Recommendations API. See that reference for every available field. The more shopper and order context you pass, the more relevant the offers Disco can return. Relevance is what drives your revenue.

Styling​

StyleOptions controls how the widget looks. All parameters are optional:

ParameterDefaultControls
appearance.lightLight theme, dark theme, or follow the device. See below
widgetBackgroundColorclearThe widget container background
slotBackgroundColortheme card colorEach offer card's background
slotVerticalSpacing16Vertical space between offer cards
slotSidePadding0Horizontal padding around cards
acceptButtonBackgroundColor / acceptButtonTextColorDisco green / whiteThe offer CTA button
promoCodeBackgroundColorDisco greenThe promo code chip
pullupBackgroundColortheme card colorThe pullup sheet background
pullupOverlayTitlenoneTitle text on the pullup sheet
fontFamilysystemCustom font family. Add the custom font to the app bundle and Info.plist

Dark mode (iOS SDK 1.0.10+)​

If your app renders in dark mode, the widget can match it. Pass an appearance:

  • .light: the default; matches historical behavior
  • .dark
  • .system: follow the device setting
Disco.execute(
attributes: attributes,
placement: .inline(embeddedView),
style: .init(appearance: .system)
)

The appearance drives the widget's theme: text and background colors follow it automatically, and any colors you've explicitly set in StyleOptions are respected as overrides.

Callbacks​

All callbacks are called on the main thread and are safe to update your user interface from.

CallbackWhen it fires
onLoadThe placement loaded and was added to the user interface
onUnloadThe placement completed and was removed from the user interface
onErrorLoading failed. Receives a Swift Error; onLoad/onUnload won't fire
onShouldShowLoadingIndicatorLoading began. Show your progress view
onShouldHideLoadingIndicatorLoading finished or failed. Hide your progress view

Errors passed to onError:

  • initNotCalled: Disco.initSdk was not called before calling execute
  • placementLoadError: an error occurred while loading the placement from the Disco backend

Widget events​

Subscribe to widget events with Disco.events(layoutId:handler:), passing the layout ID sent in your attributes:

Disco.events(layoutId: "YOUR_LAYOUT_ID") { event in
// Use event
}
EventMeaning
PlacementInteractiveOver 50% of the placement has been visible in the window for 1 second
PlacementCompletedThe placement had a single hero slot, the user engaged with it, and the placement was removed
PlacementFailureThe placement failed to load. Inspect the error property

SwiftUI​

To embed a placement in a SwiftUI view use DiscoEmbeddedSwiftUIView. The parameters are the same as calling Disco.execute:

struct ContentView: View {
var body: some View {
ZStack {
DiscoEmbeddedSwiftUIView(
attributes: attributes,
style: .init(),
config: .init(),
onLoad: { print("onLoad") },
onUnload: { print("onUnload") },
onError: { print("\($0)") },
onShouldShowLoadingIndicator: { print("show loading") },
onShouldHideLoadingIndicator: { print("hide loading") },
isSandbox: true
)
}
.padding()
.ignoresSafeArea()
}
}

Crash reporting (dSYMs)​

To symbolicate crash logs, download the latest symbol files from the package releases and upload the .dSYM files to your crash reporting tool (e.g., Crashlytics, Sentry).

Android​

Install​

The Disco Android SDK is deployed via Maven Central. In build.gradle.kts:

dependencies {
implementation("com.disconetwork:discosdk:1.+")
}

Initialize​

Initialize once at app launch, from onCreate in your Application subclass:

class App : Application() {
override fun onCreate() {
super.onCreate()
Disco.initSdk(this, "API_KEY")
}
}

Your API key is your ad-serving key, provided by your Disco team.

Add an inline placement​

Add a DiscoEmbeddedView to your XML layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">

<com.disconetwork.discosdk.DiscoEmbeddedView
android:id="@+id/discoView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

</LinearLayout>

Set layout_height to wrap_content so the DiscoEmbeddedView can update its height to match the size of the placement. If you're not using an XML layout, pass a programmatically created DiscoEmbeddedView instead.

Show offers​

Call Disco.execute when your surface is ready:

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

Disco.execute(
this,
attributes,
Placement.Inline(findViewById(R.id.discoView))
)
}
}

Pass a context, your session attributes, and a reference to the DiscoEmbeddedView; everything else is optional. The full signature:

Disco.execute(
context: Context,
attributes: Map<String, Any>,
placement: Placement,
style: StyleOptions = StyleOptions(),
onLoad: (() -> Unit)? = null,
onUnload: (() -> Unit)? = null,
onException: ((DiscoException) -> Unit)? = null,
onShouldShowLoadingIndicator: (() -> Unit)? = null,
onShouldHideLoadingIndicator: (() -> Unit)? = null,
isSandbox: Boolean = false
)

attributes follows the same request model as the Recommendations API, the same shape as the iOS example above.

Styling​

StyleOptions controls how the widget looks:

Disco.execute(
context = this,
attributes = attributes,
placement = Placement.Inline(findViewById(R.id.discoView)),
style = StyleOptions(slotSidePadding = 16)
)
ParameterDefaultControls
widgetBackgroundColorColor.TRANSPARENTThe widget container background
slotBackgroundColorColor.WHITEEach offer card's background
slotVerticalSpacing16dpVertical space between offer cards
slotSidePadding0dpHorizontal padding around cards
acceptButtonBackgroundColor / acceptButtonTextColorDisco green / whiteThe offer CTA button
promoCodeBackgroundColorDisco greenThe promo code chip
fontRobotoCustom font. Add font files to res/font and use CustomFont.ResourcePack, or add to assets and use CustomFont.AssetPack

Callbacks​

All callbacks are called on the main thread and are safe to update your user interface from. Same set as iOS, with one naming difference: errors arrive via onException, which receives a DiscoException:

  • INIT_NOT_CALLED: Disco.initSdk was not called before calling execute
  • PLACEMENT_LOAD_ERROR: an error occurred while loading the placement from the Disco backend

Widget events​

Disco.events("YOUR_LAYOUT_ID") {
Log.d("Disco", "$it")
}

Same events as iOS: PlacementInteractive, PlacementCompleted, and PlacementFailure (inspect the exception property).

Testing​

On both platforms, set isSandbox: true on Disco.execute while testing: sandbox requests render real offers but don't affect reporting or attribution.

Disco.execute(attributes: attributes, placement: .inline(embeddedView), isSandbox: true)

Pair it with your _sandbox_ key against staging. Your Disco team verifies test requests arrive complete before anything goes live (see Build & verify).