Mediation

Creating a Module

Core SDK Dependency ๐Ÿ”—

All modules in the Chartboost ecosystem must depend upon the Core SDK. This is to ensure that the module you develop can be initialized by the Core SDK. Include the following dependency to your module:

com.chartboost:chartboost-core-sdk:1.0.0

Required Module API Implementations ๐Ÿ”—

Module ๐Ÿ”—

The moduleโ€™s initialization entry point must conform to the Module interface so that the module is initializable by the Core SDK.

Your module implementation should gracefully handle situations where multiple initialization attempts are made. If your module is a single initialization type of implementation, it is recommended to synchronize your initialization method to prevent multithreaded access and to maintain an initialization state.

The interfaces are listed in the example below, however, we recommended that you double check the Core SDK API Reference.

/**
 * This interface is to be implemented by modules that can be initialized by [ChartboostCore].
 */
interface Module {
    /**
     * The ID of the module. This is recommended to be unique.
     */
    val moduleId: String

    /**
     * The version of the module. This is recommended to be a semantic version e.g. 1.0.0.
     */
    val moduleVersion: String

    /**
     * Updates the module with JSON data from the server. Publisher is recommended to
     * initialize via the constructor with module-specific parameters rather than using this function.
     * When creating a module, please make sure it's possible to send a JSONObject configuration
     * object to set up the properties of this module.
     */
    fun updateProperties(
        context: Context,
        configuration: JSONObject,
    )

    /**
     * Initialize the module.
     *
     * @param context The [Context] to use for initialization.
     * @param moduleConfiguration Configuration data for initializing the module.
     *
     * @return Result.success if the module was initialized successfully, Result.failure(Exception) otherwise.
     */
    suspend fun initialize(
        context: Context,
        moduleConfiguration: ModuleConfiguration,
    ): Result<Unit>
}
-------------------------------
/**
 * Configuration object for initializing Chartboost Core modules.
 *
 * @property chartboostApplicationIdentifier The Chartboost application identifier.
 */
data class ModuleConfiguration(val chartboostApplicationIdentifier: String)

ConsentAdapter ๐Ÿ”—

For custom CMP adapters, the moduleโ€™s initialization entry point must conform to the ConsentAdapter interface so that the module is recognized as a CMP adapter and initializable by the Core SDK.

In addition to conforming to the Module interface, the module must also conform to all of the CMP-specific methods, properties, and callbacks.

The interfaces are listed in the example below, however, we recommended that you double check the Core SDK API Reference.

/**
 * The consent management platform adapter to communicate between Chartboost Core and the CMP.
 */
interface ConsentAdapter {
    companion object {
        private const val IAB_PREFIX = "IAB"
    }

    /**
     * This tells the publisher whether or not to show a consent dialog.
     */
    val shouldCollectConsent: Boolean

    /**
     * Map of consent standards (eg. GDPR, USP, etc) to the consent value (eg. GRANTED, 1YN-, etc).
     * See [DefaultConsentKey] and [DefaultConsentValue] for other
     * common examples.
     */
    val consents: Map<ConsentKey, ConsentValue>

    /**
     * If any consent standards or status changes happen, notify this listener.
     */
    var listener: ConsentAdapterListener?

    /**
     * Show a consent dialog.
     *
     * @param activity The activity to attach the consent dialog.
     * @param dialogType The type of dialog to show.
     *
     * @return Success Result or a Result with an Exception.
     */
    suspend fun showConsentDialog(
        activity: Activity,
        dialogType: ConsentDialogType,
    ): Result<Unit>

    /**
     * Grant user consent without showing a dialog.
     *
     * @param context The Android Context.
     * @param statusSource How this consent status was retrieved.
     *
     * @return Success Result or a Result with an Exception.
     */
    suspend fun grantConsent(
        context: Context,
        statusSource: ConsentSource,
    ): Result<Unit>

    /**
     * Deny user consent without showing a dialog.
     *
     * @param context The Android Context.
     * @param statusSource How this consent status was retrieved.
     *
     * @return Success Result or a Result with an Exception.
     */
    suspend fun denyConsent(
        context: Context,
        statusSource: ConsentSource,
    ): Result<Unit>

    /**
     * Reset user consent without showing a dialog. This usually also resets the underlying
     * consent management platform and re-initializes.
     *
     * @param context The Android Context.
     *
     * @return Success Result or a Result with an Exception.
     */
    suspend fun resetConsent(context: Context): Result<Unit>

Using Environment Information ๐Ÿ”—

The ChartboostCore public API provides three predefined environment information objects for different purposes:

  • AdvertisingEnvironment
  • AnalyticsEnvironment
  • AttributionEnvironment

These environments provide information based upon an intended purpose and are grouped to provide module creators guidance on what information is permissible for a given purpose. These environments are static, and can be accessed before the Core SDK is initialized.

The environment properties located in the ChartboostCore class are listed below.

/**
 * An environment that contains information intended for advertising purposes.
 */
interface AdvertisingEnvironment {
    /**
     * The bundle identifier of this application.
     */
    val bundleIdentifier: String?

    /**
     * The device locale.
     */
    val deviceLocale: String?

    /**
     * The make or manufacturer of the device.
     */
    val deviceMake: String

    /**
     * The model of the device. This is usually a consumer product name.
     */
    val deviceModel: String

    /**
     * The operating system name. Always "Android".
     */
    val osName: String

    /**
     * The operating system version.
     */
    val osVersion: String

    /**
     * The height of the screen in pixels.
     */
    val screenHeightPixels: Int?

    /**
     * The logical density of the display. Used to convert pixels to density-independent pixels. See
     * https://developer.android.com/reference/android/util/DisplayMetrics#density
     * for more information.
     */
    val screenScale: Float?

    /**
     * The width of the screen in pixels.
     */
    val screenWidthPixels: Int?

    /**
     * Gets the advertising identifier.
     *
     * @return The advertising identifier if available.
     */
    suspend fun getAdvertisingIdentifier(): String?

    /**
     * Gets whether or not limit ad tracking is enabled.
     *
     * @return true if LAT is enabled and false otherwise.
     */
    suspend fun getLimitAdTrackingEnabled(): Boolean?

    /**
     * Add an observer to listen for changes of [ObservableEnvironmentProperty].
     */
    fun addObserver(observer: EnvironmentObserver)

    /**
     * Remove an EnvironmentObserver.
     */
    fun removeObserver(observer: EnvironmentObserver)
}
----------------------------------

/**
 * An environment that contains information intended for attribution purposes.
 */
interface AttributionEnvironment {
    /**
     * Gets the advertising identifier.
     *
     * @return The advertising identifier if available.
     */
    suspend fun getAdvertisingIdentifier(): String?

    /**
     * Gets the WebView user agent.
     *
     * @return The user agent.
     */
    suspend fun getUserAgent(): String?
}


----------------------------------

/**
 * An environment that contains information intended for analytics purposes.
 */
interface AnalyticsEnvironment {
    /**
     * The current app session duration in seconds.
     */
    val appSessionDurationSeconds: Double

    /**
     * The Chartboost-specified unique app session identifier for this app launch.
     */
    val appSessionIdentifier: String?

    /**
     * The version of the app.
     */
    val appVersion: String?

    /**
     * The wrapper or framework used to create this app. ie. Unity.
     */
    val frameworkName: String?

    /**
     * The version of the framework used to create this app.
     */
    val frameworkVersion: String?

    /**
     * Whether or not the publisher reported that the current user is underage as per COPPA.
     */
    val isUserUnderage: Boolean?

    /**
     * The type of network that is currently connected, if any.
     */
    val networkConnectionType: NetworkConnectionType

    /**
     * The publisher-specified player identifier. This is typically used for rewarding users in the
     * rewarded ad format and also for user tracking.
     */
    val playerIdentifier: String?

    /**
     * The publisher-specified app identifier. This is usually what a publisher uses to identify
     * their app.
     */
    val publisherAppIdentifier: String?

    /**
     * The publisher-specified session identifier. This is recommended to be a unique string
     * for every app launch.
     */
    val publisherSessionIdentifier: String?

    /**
     * A number [0.0, 1.0] to signify the current volume of the device.
     */
    val volume: Double?

    /**
     * Gets the WebView user agent.
     *
     * @return The user agent.
     */
    suspend fun getUserAgent(): String?

    /**
     * Gets the advertising identifier for vendors.
     *
     * @return The vendor identifier if possible.
     */
    suspend fun getVendorIdentifier(): String?

    /**
     * Gets whether the advertising identifier for vendors is sourced from the developer or from
     * the app.
     *
     * @return A [VendorIdScope] or [VendorIdScope.UNKNOWN] if not specified.
     */
    suspend fun getVendorIdentifierScope(): VendorIdScope

    /**
     * The bundle identifier of this application.
     */
    val bundleIdentifier: String?

    /**
     * The device locale.
     */
    val deviceLocale: String?

    /**
     * The make or manufacturer of the device.
     */
    val deviceMake: String

    /**
     * The model of the device. This is usually a consumer product name.
     */
    val deviceModel: String

    /**
     * The operating system name. Always "Android".
     */
    val osName: String

    /**
     * The operating system version.
     */
    val osVersion: String

    /**
     * The height of the screen in pixels.
     */
    val screenHeight: Int?

    /**
     * The logical density of the display. Used to convert pixels to density-independent pixels. See
     * https://developer.android.com/reference/android/util/DisplayMetrics#density
     * for more information.
     */
    val screenScale: Float?

    /**
     * The width of the screen in pixels.
     */
    val screenWidth: Int?

    /**
     * Gets the advertising identifier.
     *
     * @return The advertising identifier if available.
     */
    suspend fun getAdvertisingIdentifier(): String?

    /**
     * Gets whether or not limit ad tracking is enabled.
     *
     * @return true if LAT is enabled and false otherwise.
     */
    suspend fun getLimitAdTrackingEnabled(): Boolean?

    /**
     * Add an observer to listen for changes of [ObservableEnvironmentProperty].
     */
    fun addObserver(observer: EnvironmentObserver)

    /**
     * Remove an EnvironmentObserver.
     */
    fun removeObserver(observer: EnvironmentObserver)
}

Subscribing to Publisher Metadata Updates ๐Ÿ”—

Publisher metadata can be set or updated by the Publisher at any point in the appโ€™s lifecycle. If your module requires reacting to changes in these values in real time, you can subscribe to these changes by calling the AnalyticsEnvironment.addObserver() method with your EnvironmentObserver object.

Modules do not need to handle their own user consent collection, and can instead rely upon the Core SDKโ€™s consent mediation to provide that information.

To query the consent values from the currently initialized CMP, access the ConsentManagementPlatform interface from the ChartboostCore.consent property.

Consent information is valid only if a CMP adapter was successfully initialized by the Core SDK.

In the event that no CMP adapter was successfully initialized, the current consent information will be unavailable, and modules should assume that consent has not been given.

Handling Underage Users ๐Ÿ”—

CMPs do not provide COPPA related or child-directed APIs as COPPA relates to whether a user is underage or an app is child-directed, not whether the user has given consent for various use cases.

This information is captured by the isUserUnderage property in AnalyticsEnvironment, and is set by the Publisher via PublisherMetadata.setIsUserUnderage().

To obtain the consent, query the ConsentManagementPlatform.consents property for the map of current consent states.

You can then use one of the following keys in the table below to access the desired consent value if it exists. Each platform will have its own convenience constants as well.

Note that if a consent value does not exist for the specified key, it likely means that the CMP does not support that consent standard.

Consent Standard Key Description
ccpa_opt_in CCPA opt-in
gdpr_consent_given GDPR consent
IABGPP_HDR_GppString IAB GPP string
IABTCF_TCString IAB TCF string
IABUSPrivacy_String IAB USP string

Consent may change at any given time, and it is recommended to subscribe to consent changes if your module needs to react in real time to those consent changes.

To subscribe to consent changes, register your observer using the ConsentManagementPlatform.addObserver() call.

The Core SDK also provides a 500ms debouncing logic for CMP consent changes so that observers are not inundated with callbacks in a short period of time.

Usage Example ๐Ÿ”—

// Querying TCF, USP, GPP strings from ChartboostCore.consent.
val consents = ChartboostCore.consent.consents
val tcfString = consents[DefaultConsentKey.TCF]
val uspString = consents[DefaultConsentKey.USP]
val gppString = consents[DefaultConsentKey.GPP]

// Querying isUserUnderage from AnalyticsEnvironment.
val isUserUnderage = ChartboostCore.consent.analyticsEnvironment.isUserUnderage

// Subscribing to consent changes.
ChartboostCore.consent.addObserver(object : ConsentObserver {
    override fun onConsentModuleReady(initialConsents: Map<ConsentKey, ConsentValue>) {
        // The initial consent info is now ready in $fullConsents
    }

    override fun onConsentChange(fullConsents: Map<ConsentKey, ConsentValue>,
                                 modifiedKeys: Set<ConsentKey>,
    ) {
        // React to consent changes
        Log.d("Consent", "Consent changed for $modifiedKeys")
    }
})

Handling Multiple Initializations ๐Ÿ”—

It is possible that the moduleโ€™s initialization method may be invoked multiple times.

This usually occurs when the Core SDK attempts to initialize the module, and the module reports back a failure. The Core SDK will attempt to retry module initialization on an exponential backoff up to the maximum number of attempts.

As a module developer, it is good practice to add safeguards to your module initialization implementation so that you can guard against the multiple initialization edge cases such as:

  • Initializing an already initialized module.
  • Cleaning up state after failed module initializations.

Module API Access Before Module Initialization ๐Ÿ”—

Any public API may be called by a Publisher before the module has initialized.

It is good practice to identify which public APIs can function without the module being initialized vs APIs that cannot function without being initialized. For the public APIs that cannot function without being initialized, be sure to put in a fast fail initialization check.