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.
- Install the SDK
- Initialize the Data 360 and Personalization Modules
- Request Personalization
- Access Responses
- Error Handling
Install the SDK
Integrate the Personalization module into your Android project by updating your Gradle configuration files.
- Update the Personalization repository in your module-level
build.gradlefile.
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}- Add the SDK dependencies to your app’s
build.gradlefile.
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 anActivityorFragment- so the SDK is ready before any screen requests decisions. -
Use the same context instance (
this, referring to theApplication) for bothCdpConfig.BuilderandPersonalizationConfig.Builder. -
The system uses the
defaultdata 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}| Parameter | Description |
|---|---|
| personalizationPointNames | A 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)| Parameter | Description |
|---|---|
| personalizationPointNames | A 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. |
| callback | Function 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 Field | Description |
|---|---|
anchorID | The 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…” |
anchorDmoName | The 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. |
contextualAttribute | Run-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
| Field | Type | Description |
|---|---|---|
| requestId | String | The unique identifier associated with the underlying network request, useful for diagnostics. |
| personalizations | List<DecisionsResponsePersonalization> | A list of all personalization objects returned. |
| personalizationsByName | Map<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:
| Field | Type | Description |
|---|---|---|
personalizationId | String | The unique ID of the personalization. |
| personalizationPointName | String | The name of the personalization point requested. |
personalizationPointId | String | The ID of the personalization point. |
| attributes | Map<String, JsonElement> | Mapped JSON object of attributes (metadata) returned for this personalization. The schema is defined by the personalization setup. |
| data | List<DecisionsResponseContentObject> | The JSON object with content items (the actual personalized content). |
| decisionId | String? | (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}