Android Integration

Configure the Android app to identify users and manage consent settings, enabling you to request and display content tailored to specific user interactions.

  1. Install the SDK
  2. Initialize the Data 360 and Personalization Modules
  3. Request Personalization
  4. Access Responses
  5. Error Handling

Install the SDK 

Integrate the Personalization module into your Android project by updating your Gradle configuration files.

  1. Update the Personalization repository in your module-level build.gradle file.
1repositories {
2  maven {
3    url "https://salesforce-marketingcloud.github.io/MarketingCloudSDK-Android/repository"
4  }
5  maven {
6    url "https://salesforce-marketingcloud.github.io/mobile-sdk-cdp-android/repository"
7  }
8  maven {
9    url "https://salesforce-marketingcloud.github.io/Personalization-Android/repository"
10  }
11}
  1. Add the SDK dependencies to your app’s build.gradle file.
1dependencies {
2    // Personalization SDK (includes SFMC SDK and CDP SDK dependencies)
3    implementation 'com.salesforce.personalization:sdk:3.0.+'
4}
  1. To download the SDK, see Android.

Initialize the Data 360 and Personalization Modules 

Initialize the Data 360 module and the Personalization module early in your application’s lifecycle. You must configure both modules.

1import com.salesforce.marketingcloud.cdp.CdpConfig
2import com.salesforce.marketingcloud.sfmcsdk.SFMCSdk
3import com.salesforce.marketingcloud.sfmcsdk.SFMCSdkModuleConfig
4import com.salesforce.personalization.PersonalizationConfig
5
6/* Initialization of the CDP module for data collection */
7val cdpConfig = CdpConfig.Builder(
8    context = applicationContext,
9    appId = myAppId, // Your CDP Mobile Connector app ID
10    endpoint = myEndpoint // Your CDP Mobile Connector endpoint
11).build()
12
13/* Initialization of the personalization module with needed configuration */
14val personalizationConfig = PersonalizationConfig.Builder(applicationContext)
15    // Optionally define your dataspace.     // If you don't explicitly provide a data space, the SDK uses default.
16    .dataspace(myDataspace)
17    .build()
18
19/* The CDP and Personalization configurations initialized above are provided to SFMCSdk.configure  */
20SFMCSdk.configure(this, SFMCSdkModuleConfig.build {
21    cdpModuleConfig = cdpConfig
22    personalizationModuleConfig = personalizationConfig
23})
24//In production applications, explicitly manage user consent
25// using the solutions provided in the Engagement Mobile SDK
26{ initStatus ->
27    Log.d(TAG, "SDK initialized: ${initStatus.status}")
28}

Considerations 

  • The system uses the default data space if you don’t explicitly provide one.

  • To enable debug logging during development, add this snippet to the code. Make sure this setting is disabled or removed before building your application for production.

    1SFMCSdk.setLogging(DEBUG, AndroidLogger())

    For more information on logging, see Logging and Debugging.

  • Consent should be set to opt_in for using the Personalization SDK. In your production application, you must explicitly manage user consent using the solution provided in the Engagement Mobile SDK. See Data 360 Consent Management.

    1import com.salesforce.marketingcloud.cdp.CdpSdk
    2import com.salesforce.marketingcloud.cdp.consent.Consent
    3
    4// when a user consents to data collection
    5CdpSdk.requestSdk { sdk -> sdk.consent = Consent.OPT_IN }
    6
    7// when a user does not consent to data collection
    8CdpSdk.requestSdk { sdk -> sdk.consent = Consent.OPT_OUT }

Request Personalization 

The fetchDecisions method enables you to interact with the Decisioning API and request one or more personalizations for specific areas of your mobile application. The SDK provides two methods for fetching personalization decisions: suspend functions (recommended) and callbacks.

Use a Suspend Function to Fetch Decisions (Recommended, Kotlin Only) 

Call fetchDecisions within a coroutine scope to handle personalization requests asynchronously.

1import com.salesforce.personalization.PersonalizationSdk
2import com.salesforce.personalization.errors.PersonalizationException
3import com.salesforce.personalization.http.DecisionsRequestContext
4import com.salesforce.personalization.http.DecisionsResponse
5import com.salesforce.personalization.utils.Result
6
7// In a coroutine scope (ViewModel, LaunchedEffect, and so on.)
8val result = PersonalizationSdk.fetchDecisions(
9    personalizationPointNames = arrayOf("point1", "point2"),
10    // Optionally use additional context to influence the decisions.
11    // See "Request Personalization By Using Context" for details.
12    decisionsRequestContext = decisionsRequestContext, // Optional
13    timeoutMs = 10000L // Optional: 10 seconds (default: 10s)
14)
15
16when (result) {
17    is Result.Success -> {
18        // Handle successful response
19        val response: DecisionsResponse = result.output
20        // Process personalizations...
21    }
22    is Result.Failure -> {
23        // Handle error
24        val exception: PersonalizationException = result.throwable
25        Log.e(TAG, "Personalization failed: ${exception.message}")
26    }
27}
ParameterDescription
personalizationPointNamesAn array of at least one personalization point name for which you want to return decisions.
decisionsRequestContext(Optional) Additional context information. See Request Personalization By Using Context. If you don’t have any context, pass nil.
timeoutMs(Optional) The duration, in seconds, before the fetch task times out. The default is 10 seconds.

Grouping personalization point names by datagraph is the responsibility of the caller. This method does not automatically group or separate requests based on datagraph configuration. See Request Personalization.

Note

Use a Callback to Fetch Decisions 

If your architecture does not support coroutines, use the callback-based implementation of fetchDecisions to handle responses.

1import com.salesforce.personalization.PersonalizationSdk
2import com.salesforce.personalization.errors.PersonalizationException
3import com.salesforce.personalization.http.DecisionsRequestContext
4import com.salesforce.personalization.http.DecisionsResponse
5import com.salesforce.personalization.utils.Result
6
7PersonalizationSdk.fetchDecisions(
8    scope = lifecycleScope, // or viewModelScope, rememberCoroutineScope(), and so on.
9    personalizationPointNames = arrayOf("point1", "point2"),
10    // Optionally use additional context to influence the decisions.
11    // See "Request Personalization By Using Context" for details.
12    decisionsRequestContext = decisionsRequestContext,
13    timeoutMs = 10000L, // Optional: 10 seconds (default: 10s)
14    callbackDispatcher = Dispatchers.Main, // Optional dispatcher for callback. ( Defaults to Dispatchers.Main.)
15    callback = { result ->
16        when (result) {
17            is Result.Success -> {
18            // Handle successful response
19                val response: DecisionsResponse = result.output
20            // Process personalizations...
21            }
22            is Result.Failure -> {
23            // Handle error
24                val exception: PersonalizationException = result.throwable
25                Log.e(TAG, "Personalization failed: ${exception.message}")
26            }
27        }
28    }
29)
ParameterDescription
personalizationPointNamesAn array of at least one personalization point name for which you want to return decisions.
context(Optional) Additional context information. See Request Personalization By Using Context. If you don’t have any context, pass nil.
timeoutMs(Optional) The duration, in seconds, before the fetch task times out. The default is 10 seconds.
callbackDispatcher(Optional) The dispatcher for callback. The default is Dispatchers.Main.
callbackFunction that executes when the fetch operation completes. The callback receives a Result object containing either a DecisionsResponse on success or a PersonalizationException on failure.

Grouping personalization point names by datagraph is the responsibility of the caller. This method does not automatically group or separate requests based on datagraph configuration. See Request Personalization.

Note

Request Personalization By Using Context 

You can use additional context to influence personalization decisions. Use this method only if you have specific context available to affect the decisions. To know about context variables and how they can be used, see Filter Recommendations Using Dynamic Context Variables.

Construct a DecisionsRequestContext object and use the required context.

1import com.salesforce.personalization.http.DecisionsRequestContext
2
3/* Optionally use a request context */
4val decisionsRequestContext = DecisionsRequestContext.Builder()
5    // Optionally define the anchor item for your recommendations
6    .anchorId("PRODUCT_123")
7
8    // Optionally define type of an anchor item
9    .anchorDmoName("ssot__GoodsProduct__dlm")
10
11    // Optionally define the contextual attributes and their values
12    .contextualAttribute(name: "category", value: "shoes")
13    .contextualAttribute(name: "color", value: "red")
14
15    .build()
16
17)

For the DecisionsRequestContext builder, optionally add the following fields.

Optional FieldDescription
anchorIDThe unique identifier of the specific item the user is currently viewing. This is helpful in context-aware strategies, such as “People who bought this item also bought…“
anchorDmoNameThe API Name of the DMO that corresponds to the anchorId. This tells the system what kind of item the anchor is. The DMO name should correspond to the actual one set up in Data 360. Example: ssot__GoodsProduct__dlm.
contextualAttributeRun-time attributes that help refine recommendation responses and enable advanced filtering logic in your recommenders. To pass multiple attributes, you need to chain a separate method call for each specific key-value pair. For information on contextual attributes, see Filter Recommendations Using Dynamic Context Variables.

The fetchDecisions method interacts with the Decisioning API and requests one or more personalizations for specific areas of your mobile application.

1import com.salesforce.personalization.PersonalizationSdk
2import com.salesforce.personalization.http.DecisionsRequestContext
3
4// In a coroutine scope (suspend function)
5val result = PersonalizationSdk.fetchDecisions(
6    personalizationPointNames = arrayOf("Hero_Banner", "Footer_Promo"),
7    decisionsRequestContext = decisionsRequestContext
8    // Applies to ALL personalization points above
9)

Access Responses 

The fetchDecisions method returns a DecisionsResponse object. This object contains both the raw list of personalizations and an optimized dictionary lookup.

DecisionsResponse Structure 

FieldTypeDescription
requestIdStringThe unique identifier associated with the underlying network request, useful for diagnostics.
personalizations[DecisionsResponsePersonalization]An array of all personalization objects returned.
personalizationsByNameMap<String, DecisionsResponsePersonalizationA dictionary of personalization points indexed by name. We recommend this instead of an array for efficient access.

DecisionsResponsePersonalization Structure 

Each item in the personalizations array (accessible via personalizationsByName) represents the content returned for a specific personalization point:

FieldTypeDescription
personalizationIdStringThe unique ID of the personalization.
personalizationPointNameStringThe name of the personalization point requested.
personalizationPointIdStringThe ID of the personalization point.
attributesMap<String, JsonElement>Mapped JSON object of attributes (metadata) returned for this personalization. The schema is defined by the personalization setup.
data[DecisionsResponseContentObject]The JSON object with content items (the actual personalized content).
decisionIdString?(Optional) The unique identifier for the specific decision made by the server.

Example to Access Response Content 

1import com.salesforce.personalization.http.DecisionsResponse
2import com.salesforce.personalization.http.DecisionsResponsePersonalization
3import com.salesforce.personalization.http.DecisionsResponseContentObject
4
5val response: DecisionsResponse = result.output
6
7// Using lookup (preferred)
8val homeHero = response.personalizationsByName["homeHero"]
9
10if (homeHero != null) {
11    // Access attributes
12    // Safe string extraction
13    val intro = homeHero.attributes["IntroductionText"]
14        ?.takeIf { it is JsonPrimitive && it.isString }
15        ?.let { (it as JsonPrimitive).content }
16
17    // Safe number extraction
18    val priority = homeHero.attributes["priority"]
19        ?.takeIf { it is JsonPrimitive && it.isString }
20        ?.let { (it as JsonPrimitive).content?.toIntOrNull() }
21
22    // Access content objects
23    homeHero.data.forEach { contentObj ->
24        val contentId = contentObj.personalizationContentId
25
26        val title = contentObj["title"]
27            ?.takeIf { it is JsonPrimitive && it.isString }
28            ?.let { (it as JsonPrimitive).content }
29            ?: "Default Title"
30
31        val imageUrl = contentObj["imageUrl"]
32            ?.takeIf { it is JsonPrimitive && it.isString }
33            ?.let { (it as JsonPrimitive).content }
34
35        // Check if key exists
36        if (contentObj.containsKey("description")) {
37            val description = contentObj["description"]
38                ?.takeIf { it is JsonPrimitive && it.isString }
39                ?.let { (it as JsonPrimitive).content }
40        }
41
42        // Get all available keys
43        val allKeys = contentObj.keys()
44    }
45}
46
47// Iterate all personalizations
48response.personalizations.forEach { personalization ->
49    println("Point: ${personalization.personalizationPointName}")
50    println("Items: ${personalization.data.size}")
51}

In the example, fetchDecisions sends the user context to the server to request content for both homeHero and product_recs simultaneously. After the response arrives, personalizationsByName allows you to access the results for each point independently.

Error Handling 

Implement error handling for decision fetching. We recommend logging the exception.

1import com.salesforce.personalization.PersonalizationSdk
2import com.salesforce.personalization.errors.PersonalizationException
3import com.salesforce.personalization.http.DecisionsResponse
4import com.salesforce.personalization.utils.Result
5
6// Simple Error Handling (Recommended)
7
8val result = PersonalizationSdk.fetchDecisions(arrayOf(pointName))
9when (result) {
10    is Result.Success -> {
11        // Handle success
12        val response = result.output
13        // Process personalizations...
14    }
15    is Result.Failure -> {
16        // Log the error - no need to differentiate exception types for most use cases
17        Log.e(TAG, "Personalization failed: ${result.throwable.message}", result.throwable)
18    }
19}
20
21// Detailed Error Handling (Optional)
22
23val result = PersonalizationSdk.fetchDecisions(arrayOf(pointName))
24when (result) {
25    is Result.Success -> {
26        // Handle success
27    }
28    is Result.Failure -> {
29        val exception = result.throwable
30
31        // Optional: Differentiate by exception type if needed
32        when (exception.type) {
33            PersonalizationException.Type.TIMEOUT -> {
34                Log.e(TAG, "Request timeout: ${exception.reason}")
35                // Show retry option to user
36            }
37            PersonalizationException.Type.NETWORK -> {
38                Log.e(TAG, "Network error: ${exception.reason}")
39                // Show offline message
40            }
41            else -> {
42                Log.e(TAG, "Personalization error: ${exception.message}")
43            }
44        }
45    }
46}