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.+'
4}

Initialize the Data 360 and Personalization Modules 

The host app owns SDK initialization. Initialize both the Data 360 module and the Personalization module in your Application.onCreate(), before making any fetchDecisions calls.

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 flushRate = EventFlushRate.Quantity(quantity = 1)
8val cdpConfig = CdpConfig.Builder(
9    context = this,
10    appId = "<Your CDP App ID>", // Your CDP Mobile Connector app ID
11    endpoint = "<Your CDP Endpoint>" // Your CDP Mobile Connector endpoint
12).eventFlushRate(flushRate)
13 .build()
14
15/* Initialization of the personalization module with needed configuration */
16val personalizationConfig = PersonalizationConfig.Builder(this)
17    // Optionally define your dataspace. The "default" data space is used if you don't explicitly provide one.
18    .dataspace("myDataspaceApiName")
19    .build()
20
21/* The CDP and Personalization configurations initialized above are provided to SFMCSdk.configure */
22SFMCSdk.configure(this, SFMCSdkModuleConfig.build {
23    cdpModuleConfig = cdpConfig
24    personalizationModuleConfig = personalizationConfig
25})
26{ initStatus ->
27    Log.d(TAG, "SDK initialized: ${initStatus.status}")
28}

In your production application, you must explicitly manage user consent using the solution provided in the Engagement Mobile SDK. See Data 360 Consent Management.

Note

Considerations 

  • Call this initialization once, from Application.onCreate() - not from an Activity or Fragment - so the SDK is ready before any screen requests decisions.

  • Use the same context instance (this, referring to the Application) for both CdpConfig.Builder and PersonalizationConfig.Builder.

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

  • To enable debug logging during development, add this snippet before SFMCSdk.configure(...), so logging is active for the whole init sequence. 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.

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 = listOf("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
personalizationPointNamesA list 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 = listOf("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
personalizationPointNamesA list 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()

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. If omitted, the system infers the anchor type from anchorId automatically.
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 = listOf("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.
personalizationsList<DecisionsResponsePersonalization>A list of all personalization objects returned.
personalizationsByNameMap<String, DecisionsResponsePersonalization>A dictionary of personalization points indexed by name. We recommend this instead of a list for efficient access.

DecisionsResponsePersonalization Structure 

Each item in the personalizations list (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.
dataList<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(listOf(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(listOf(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}