Android Jetpack Compose SDK (25.3.0)
Introduction
The Atomic Jetpack Compose SDK is an Android SDK for integrating an Atomic stream container into your Android app, presenting cards from a stream to your users. Built with Jetpack Compose at its core and written entirely in Kotlin, it delivers a seamless integration experience for Jetpack Compose apps.
The latest stable release of the SDK is 25.3.0.
The Android Jetpack Compose SDK is replacing the existing Android SDK, which is now in support mode.
We encourage you to try it out and contact us with any feedback or issues that you experience.
Supported Android versions
The SDK targets a minimum SDK version of 24.
Installation
The SDK can be installed as a Gradle dependency.
The SDK requires a minimum Android Gradle Plugin version of 8.6.0.
Gradle
The SDK is hosted on our public Maven repository. You'll need to add the following repository to your root build.gradle or settings.gradle file depending on how your project is set up:
repositories {
maven {
url "https://downloads.atomic.io/android-sdk/maven"
}
}
Then, add the following to your app’s build.gradle
dependencies {
implementation 'io.atomic.actioncards:aaccore:25.3.0'
implementation 'io.atomic.actioncards:aaccompose:25.3.0'
}
Core library desugaring
In order to use modern Java time APIs in older Android versions, we enable core library desugaring for the Compose SDK. In order to use the Atomic SDK in your app, you must also enable core library desugaring. https://developer.android.com/studio/write/java8-support#library-desugaring.
Initializing the SDK
Before using the SDK, you must log in by providing your environmentId, apiKey, and apiHost. These can be found in the Atomic Workbench.
You may call this method only when the user has authenticated in your app. But be careful to ensure it is called in all cases before showing the Atomic Stream Container. For example, check that it is called when opening the app after process death, or from a notification intent.
The SDK uses a JSON Web Token (JWT) to perform authentications.
The SDK Authentication guide provides step-by-step instructions on how to generate a JWT and add a public key to the Workbench.
You must also provide an authentication token delegate to provide the JWT token. If a token cannot be provided, return an empty string.
The complete code snippet for initializing the SDK is:
AACCore.shared.login(environmentId, apiKey, apiHost) { onTokenReceived ->
// Fetch your token here
// Then return it by calling onTokenReceived(token)
onTokenReceived(token)
}
Log out
Logout will effectively end the user's session. It will clear theme and card caches, send any pending analytics events to the platform, and then log out the current user. The session delegate will also be reset and SDK settings cleared, which will cease any network traffic.
You must call logout() before switching users in your application, even if there are no other changes to the configuration.
To log out, call:
val result = AACCore.shared.logout()
Displaying containers
StreamContainerConfiguration is a central configuration class used to customize the behavior and appearance of all Atomic containers in the SDK. By creating and modifying a StreamContainerConfiguration instance, you can control a wide range of options such as UI elements, refresh intervals, card styling, custom strings, and event handling.
You typically create a StreamContainerConfiguration object in your view model or view, set its properties or call its methods to adjust the container's features, and then pass it to the container you are displaying. Each container type accepts a configuration object as an optional parameter. If omitted, the container will use default settings.
Below are the main configuration options available in StreamContainerConfiguration:
Style and presentation
interfaceStyle: The interface style (light, dark, or automatic) to apply to the stream container (default:InterfaceStyle.Automatic).toastMessagesEnabled: Show toast messages at the bottom of the screen (default: true).cardListHeaderEnabled: Show a header at the top of the vertical card list (default: true).
Custom strings
Set custom strings by setting the following keys on the StreamContainerConfiguration to a non-empty string. Set to null to revert to the SDK default.
cardListTitle: The title to display at the top of the card list (default: "Cards").cardSnoozeTitle: The title to display for the card snooze functionality (default: "Remind me").votingFeedbackTitle: Title displayed at the top of the screen where users can provide feedback on why they found a card useful or not useful (default: "Send feedback").noInternetMessage: Error message for no internet (default: "No internet connection").toastCardDismissMessage: Toast for card dismissed (default: "Card dismissed").toastCardCompletedMessage: Toast for card completed (default: "Card completed").toastCardSnoozeMessage: Toast for card snoozed (default: "Snoozed until X").toastCardFeedbackMessage: Toast for card feedback (default: "Feedback received").thumbnailImageActionLinkText: CTA for thumbnail images (default: "View").votingFeedbackValidationMessage: Validation message shown when the user reaches the 280 character limit while providing feedback on the feedback page (default: "Feedback is limited to 280 characters").
You will find usage examples for StreamContainerConfiguration throughout this guide, in the context of each container type.
Displaying a vertical container
The Atomic SDK provides support for rendering a vertical stream container within your host application. The vertical container displays cards arranged from top to bottom.
To use this feature, in your Composable function, call the StreamContainer composable, supplying the following arguments:
- modifier: A Compose modifier to specify the layout of the container, you must specify a width either .fillMaxWidth() or an explicit width.
- container ID: Identifier for the stream container you wish to display.
- Configuration (optional): Defines the desired appearance and behavior of the stream container.
- Handlers (optional): Defines some callbacks for calling your own code based on events or actions within the stream container.
The following code snippet displays a vertical container using default configurations:
StreamContainer(
modifier = Modifier.fillMaxWidth(),
containerId = "ABC1234",
configuration = StreamContainerConfiguration(),
handlers = StreamContainerHandlers()
)
To modify your configuration object, either set the values using the data class initializer. For example,
val configuration = StreamContainerConfiguration(
toastMessagesEnabled = false,
cardListTitle = "Notifications"
)
Or make a copy of an existing configuration, passing the new values.
val newConfiguration = configuration.copy(
cardListTitle = "My messages"
)
Passing a new configuration class is required to update the UI, mutating the variables in place will not update them.
Displaying a single card container
The Atomic SDK also supports rendering a single card in your host app. The single card container displays a single card at a time, and collapses to take up no space when there are no cards to display in the stream.
Single card containers will expand vertically to take up the space required by the card. You should not set an explicit height with the modifier. Unlike the vertical container which is designed to take up the whole view, you may place a single card container inside of a scrollable column.
To display a SingleCardView, which is configured in the same way as a vertical stream container, you supply the following arguments to the SingleCardView composable:
- modifier: A Compose modifier to specify the layout of the container, you must specify a width either .fillMaxWidth() or an explicit width.
- container ID: Identifier for the stream container you wish to display.
- Configuration (optional): Defines the desired appearance and behavior of the stream container.
- Handlers (optional): Defines some callbacks for calling your own code based on events or actions within the stream container.
The following code snippet displays a single card container using default configurations:
SingleCardView(
modifier = Modifier.fillMaxWidth(),
containerId = "ABC1234",
configuration = StreamContainerConfiguration(),
handlers = StreamContainerHandlers()
)
Displaying a horizontal container
The Atomic Compose SDK provides support for rendering a horizontal stream container within your host application. The horizontal container displays cards arranged from left to right with the same height.
To use this feature, create a HorizontalStreamContainer composable, supplying the following arguments.
- modifier: A Compose modifier to specify the layout of the container, you must specify a width either .fillMaxWidth() or an explicit width.
- container ID: The identifier for the stream you wish to display.
- Card width: The width (in Dp) of each card displayed (must be greater than 0).
- Configuration (optional): Defines the desired behavior and appearance of the horizontal container.
- Handlers (optional): Defines some callbacks for calling your own code based on events or actions within the stream container.
The available configuration options align with those of a standard stream container. However, please note that certain properties are not applicable to horizontal containers. For detailed information, refer to the definition file of ContainerConfiguration.
The following example illustrates how to place a horizontal container using a card width of 250 dp and the default configuration.
HorizontalStreamContainer(
modifier = Modifier.fillMaxWidth().onSizeChanged { println("Horizontal size changed to $it") }.animateContentSize(),
containerId = "ABC1234",
cardWidth = 250.dp,
configuration = StreamContainerConfiguration(),
handlers = StreamContainerHandlers()
)
Note: The swipe gesture is disabled in horizontal containers.
Configuration options for a horizontal container
In addition to common properties from StreamContainerConfiguration, the following properties are specifically designed for horizontal containers:
-
horizontalTitleAlignment: Sets the alignment of the title in the horizontal header. Possible values:Center: Default value. Title is centered within the header.Leading: Title is aligned to the leading edge of the header.
-
horizontalEmptyStyle: Defines how the container appears when no cards are present. Possible values:Standard: Default value. Displays a UI indicating no cards are available.Shrinking: The container shrinks when there are no cards.
-
horizontalScrollMode: Controls scrolling behavior in the container. Possible values:Snap: Enables snapping, positioning each card in the center of the viewport when scrolling is finished. (Default).Free: The container scrolls freely without snapping.
-
horizontalLastCardAlignment: Aligns the card when only one card is present in the container. Possible values:Leading: Default value. Aligns the card to the leading edge.Center: Aligns the card to the center of the container.ScaleToFill: Scales the card's width to fill the container with default padding.
Displaying multiple stream containers or single card views in one ViewModel scope
The Compose SDK uses the Androidx ViewModel class to persist state across configuration changes, like rotation. By default, we retrieve these ViewModels from the default view model store without any specific key. This does mean, that by default, only one stream container or single card view can be displayed on each page or scope.
To support multiple containers, for example to have two single card views embedded in a scrollable view, you need to create and persist (normally in a ViewModel) a unique string identifier for each container and provide them using the key: String? argument when creating the Composable.
For example, this code snippet creates a vertically scrollable column, with a ViewModel which stores two unique identifiers for two single card views which each display cards from two different stream containers.
Composables
Column(Modifier.verticalScroll(rememberScrollState())) {
val multipleContainerViewModel = viewModel {
MultipleContainerViewModel()
}
Spacer(Modifier.height(80.dp).fillMaxWidth().background(MaterialTheme.colorScheme.surfaceVariant))
SingleCardView(
modifier = Modifier.fillMaxWidth().onSizeChanged { println("Single card size changed to $it") }.animateContentSize(),
containerId = "ABCDEF",
configuration = StreamContainerConfiguration(),
handlers = StreamContainerHandlers(),
key = multipleContainerViewModel.key
)
Spacer(Modifier.height(80.dp).fillMaxWidth().background(MaterialTheme.colorScheme.surfaceVariant))
Spacer(Modifier.height(180.dp).fillMaxWidth().background(MaterialTheme.colorScheme.surface))
Spacer(Modifier.height(80.dp).fillMaxWidth().background(MaterialTheme.colorScheme.surfaceVariant))
SingleCardView(
modifier = Modifier.fillMaxWidth().onSizeChanged { println("Single card size changed to $it") }.animateContentSize(),
containerId = "GHIJKL",
configuration = StreamContainerConfiguration(),
handlers = StreamContainerHandlers(),
key = multipleContainerViewModel.secondKey
)
Spacer(Modifier.height(80.dp).fillMaxWidth().background(MaterialTheme.colorScheme.surfaceVariant))
Spacer(Modifier.height(180.dp).fillMaxWidth().background(MaterialTheme.colorScheme.surface))
}
ViewModel
class MultipleContainerViewModel(): ViewModel() {
val key: String by lazy {
UUID.randomUUID().toString()
}
val secondKey: String by lazy {
UUID.randomUUID().toString()
}
}
Customizing the first time loading experience
When a stream container with a given ID is launched for the first time on a user's device, the SDK loads the theme and caches it for future use. On subsequent launches of the same stream container, the cached theme is used and the theme is updated in the background, for the next launch. Note that this first time loading screen is not presented in single card view - if a single card view fails to load, it collapses to a height of 0.
The SDK supports some basic properties to style the first time load screen, which displays a loading spinner in the center of the container. If the theme fails to load for the first time, an error message is displayed with a 'Try again' button. Along with an error message "No internet connection".
First time loading screen colors are customized using the following properties on StreamContainerConfiguration:
launchBackgroundColor: the background of the first time loading screen. Defaults to Color.Transparent.launchTextColor: the color to use for the error message shown when the theme fails to load. Defaults to Color.Black.launchLoadingColor: the color to use for the loading spinner on the first time loading screen. Defaults to Color.Black.launchButtonColor: the color to use for the 'Try again' button, shown when the theme fails to load. Defaults to Color.Black.
You can also customize the text for the first load screen error message and the 'Try again' button, using the following properties.
Note: These customized error messages also apply to the card list screen.
noInternetMessage: the error message shown when the user does not have an internet connection.tryAgainButtonTitle: the title of the button allowing the user to retry the failed request for the card list or theme.
Maximum card width
You can specify a maximum width for each card within the vertical stream container with specified alignments for the card(s).
To set this, use the cardMaxWidth property in StreamContainerConfiguration to define the desired width and alignment, and apply this configuration when initializing the stream container.
The default value for cardMaxWidth is Fill, which means the card will automatically adjust its width to match that of the stream container with horizontal padding.
Another possible value is Fixed(width, alignment), where width is the maximum width of the card(s), and alignment is value that you can supply to position the card within its container. Omit alignment to keep cards centred.
However, there are a few considerations for setting the width:
- Setting a value below
200display points is not recommended. - Negative values are not supported.
Note: This option has no effect in horizontal containers.
The following code snippet creates a configuration to set the maximum card width to 500 with a centre alignment.
val configuration = StreamContainerConfiguration(
cardMaxWidth = CardMaxWidthStrategy.Fixed(500.dp)
)
The following code snippet sets the maximum card width to 500 with a leading alignment.
val configuration = StreamContainerConfiguration(
cardMaxWidth = CardMaxWidthStrategy.Fixed(500.dp, CardMaxWidthStrategy.Alignment.Leading)
)
Supporting custom actions on submit and link buttons
In the Atomic Workbench, you can create a submit or link button with a custom action payload.
- When such a Link button is tapped, the
linkButtonWithPayloadActionHandlercallback is invoked. - When such a Submit button is tapped, and after the card is successfully submitted, the
submitButtonWithPayloadActionHandlercallback is invoked.
These callbacks are passed a custom action object, containing the payload that was defined in the Workbench for that button. You can use this payload to determine the action to take, within your app, when the submit or link button is tapped.
The action object also contains the card instance ID and stream container ID where the custom action was triggered.
val handlers = StreamContainerHandlers(
linkButtonWithPayloadActionHandler = { aacCardCustomAction ->
println(aacCardCustomAction.streamContainerId)
println(aacCardCustomAction.cardInstanceId)
println(aacCardCustomAction.payload)
},
submitButtonWithPayloadActionHandler = { aacCardCustomAction ->
println(aacCardCustomAction.streamContainerId)
println(aacCardCustomAction.cardInstanceId)
println(aacCardCustomAction.payload)
}
)
Custom Link actions triggered from subviews
Because Atomic launches it's own activity to display subviews, you may find that you are unable to use your apps programmatic navigation methods in response to payload link actions when they are used within a subview.
For example, you may want to navigate the user to a specific page when a link button with a payload is tapped.
In order to support this case, the AACCustomCardAction class has a property isSubview, which will be true when the action is triggered on a subview. You can then either return an object, in which case the subview will finish and the block of code provided in the object will then be executed.
Alternatively, return null and the subview will stay open.
val streamContainerHandlers = StreamContainerHandlers(
linkButtonWithPayloadActionHandler = { cardCustomAction ->
if (!cardCustomAction.isSubviewAction) {
// We can check if this handler is being triggered from a subview or not.
// If this is not a subview action, then we can run our block of code here
}
// We can return an AACLinkButtonSubviewAction with some code to be called after the subview
// completes
return@StreamContainerHandlers AACLinkButtonSubviewAction({
// Code here will be called after the Subview finishes and we are returned to the original
// activity
})
// Alternatively, we can return null, and the subview will stay open
return@StreamContainerHandlers null
},
submitButtonWithPayloadActionHandler = {
// Submitting a card automatically finishes the activity, the handler will be called after this
}
)
Customizing toast messages for card events
You can customize any of the toast messages used when dismissing, completing, snoozing and placing feedback on a card. This is configurable for each stream container. You supply a string for each custom message. If you do not supply a string the defaults will be used.
Options are:
toastCardDismissMessage: Customized toast message for when the user dismisses a card - defaults to "Card dismissed"toastCardCompletedMessage: Customized toast message for when the user completes a card - defaults to "Card completed"toastCardSnoozeMessage: Customized toast messages for when the user snoozes a card - defaults to "Snoozed until X" whereXis the time the user dismissed the card until.toastCardFeedbackMessage: Customized toast message for when the user sends feedback (votes) for a card - defaults to "Feedback received"
val config = StreamContainerConfiguration()
config.toastCardDismissMessage = "The card has been dismissed"
config.toastCardCompletedMessage = "The card has been completed"
config.toastCardSnoozeMessage = null // this will show the default. Assigning null is optional
config.toastCardFeedbackMessage = "Thanks for the feedback"
You can also disable toast messages entirely by setting toastMessagesEnabled to false.
Card snoozing
The Atomic SDKs provide the ability to snooze a card from a stream container or single card view. Snooze functionality is exposed through the card’s action buttons, overflow menu and the quick actions menu (exposed by swiping a card to the left).
Tapping on the snooze option from either location brings up the snooze date and time selection screen. The user selects a date and time in the future until which the card will be snoozed. Snoozing a card will result in the card disappearing from the user’s card list or single card view, and reappearing again at the selected date and time. A user can snooze a card more than once.
When a card comes out of a snoozed state, if the card has an associated push notification, and the user has push notifications enabled, the user will see another notification, where the title is prefixed with Snoozed:.
You can customize the title of the snooze functionality, as displayed in a card’s overflow menu and in the title of the card snooze screen. The default title, if none is specified, is Remind me.
On the StreamContainerConfiguration class, set cardSnoozeTitle to your own custom value.
StreamContainerConfiguration(cardSnoozeTitle = "My title")
Dark mode
Stream containers and single card views support dark mode. You configure an (optional) dark theme for your stream container in the Atomic Workbench.
The interface style determines which theme is rendered:
InterfaceStyle.Automic: If the user's device is currently set to light mode, the stream container will use the light (default) theme. If the user's device is currently set to dark mode, the stream container will use the dark theme (or fallback to the light theme if no dark theme is configured). On Android versions that don't support night mode, this setting is equivalent toAACInterfaceStyle.LIGHT.InterfaceStyle.Light: The stream container will always render in light mode, regardless of the device setting.InterfaceStyle.Dark: The stream container will always render in dark mode, regardless of the device setting.
To change the interface style, set the corresponding value for the interfaceStyle property on your StreamContainerConfiguration.
Retrieving the count of active and unseen cards
-
Unseen cards are cards that have been sent to the user but have not yet been shown on their screen. A card becomes “seen” once it is displayed to the user (depending on scroll speed and visibility, a quick scroll-through might not make the card "seen").
-
Active cards are cards currently available to the user. Cards that are snoozed or embargoed are not included in this count.
The Atomic Android SDK exposes a User Metrics object that provides counts of cards available to a user, both across all stream containers and within specific containers.
These metrics allow you to display counts or badges in your UI that indicate:
- The total number of cards available to the user across all containers
- The number of cards not yet seen by the user across all containers
- The total number of cards available in a specific stream container
- The number of unseen cards in a specific stream container
Retrieving user metrics
To retrieve user metrics, call the userMetrics method.
This method will request the metrics for the current logged in user and invokes your callback with a UserMetricsResult indicating success or failure.
On success, the callback receives an UserMetricsResult object that exposes the card counts.
The following example demonstrates how to request user metrics and handle both success and error results:
AACCore.shared.userMetrics { result ->
when (result) {
is UserMetricsResult.Success -> {
val metrics = result.userMetrics
Log.d("User metrics", "Total active cards: ${metrics.totalCards}")
Log.d("User metrics", "Unseen cards: ${metrics.unseenCards}")
Log.d("User metrics", "Active cards in container: ${metrics.totalCardsForStreamContainer("containerId")}")
Log.d("User metrics", "Unseen cards in container: ${metrics.unseenCardsForStreamContainer("containerId")}")
}
is UserMetricsResult.DataError -> {
Log.e("User metrics", "Failed to fetch user metrics: ${result.message}")
}
}
}
Runtime variables
Runtime variables are resolved in the SDK at runtime, rather than from an event payload when the card is assembled. Runtime variables are defined in the Atomic Workbench.
The SDK will ask the host app to resolve runtime variables when a list of cards is loaded (and at least one card has a runtime variable), or when new cards become available due to WebSocket push or HTTP polling (and at least one card has a runtime variable).
Runtime variables are resolved by your app via the cardDidRequestRunTimeVariablesHandler method on StreamContainerHandlers. If this method is not implemented on StreamContainerHandlers, runtime variables will fall back to their default values, as defined in the Atomic Workbench. To resolve runtime variables, you pass an object that is StreamContainerHandlers when creating a stream container or a single card view, and define cardDidRequestRunTimeVariablesHandler in that object.
Runtime variables can currently only be resolved to string values.
The cardDidRequestRunTimeVariablesHandler method, when called by the SDK, provides you with:
- The identifier of the container the cards belong to.
- A list of objects representing the cards in the list. Each card object contains:
- The event name that triggered the card’s creation;
- The lifecycle identifier associated with the card;
- A method that you call to resolve each variable on that card (
resolve(variableName, value)) and returns a new card object.
- A block callback (
completion) which must be called by the host app, with the resolved cards, once all variables are resolved.
If a variable is not resolved, that variable will use its default value, as defined in the Atomic Workbench.
If you do not call the completion before the runtimeVariableResolutionTimeout elapses (defined on StreamContainerConfiguration), the default values for all runtime variables will be used. Calling the completion handler more than once has no effect.
val handlers = StreamContainerHandlers(
cardDidRequestRunTimeVariablesHandler = { _, cards, completion ->
val resolved = cards.map { card ->
card
.resolve("amount", "500")
.resolve("currency", "NZD")
}
completion(resolved)
}
)
Runtime variable resolution timeout
The default timeout for runtime variable resolution is 5 seconds (5000 milliseconds). This timeout cannot be negative.
The runtimeVariableResolutionTimeout setting controls the maximum amount of time the SDK will wait for your cardDidRequestRunTimeVariablesHandler to complete and return resolved cards. If the timeout is reached before the completion callback is invoked, the SDK will automatically fall back to the default values defined in the Atomic Workbench for all runtime variables.
If your runtime variable resolution logic requires network requests or other asynchronous operations that may take longer than the default timeout, you can increase this value on your StreamContainerConfiguration object:
StreamContainerConfiguration(
runtimeVariableResolutionTimeout = 10000 // 10 seconds
)
Runtime variables analytics
The default behavior is to not send analytics for resolved runtime variables. Therefore, you must explicitly enable this feature to use it.
If you use runtime variables on a card, you can optionally choose to send the resolved values of any runtime variables back to the Atomic Platform as an analytics event. This per-card analytics event - runtime-vars-updated - contains the values of runtime variables rendered in the card and seen by the end user. Therefore, you should not enable this feature if your runtime variables contain sensitive data that you do not wish to store on the Atomic Platform.
To enable this feature, set the runtimeVariableAnalyticsEnabled flag on your StreamContainerConfiguration configuration object:
StreamContainerConfiguration(
runtimeVariableAnalyticsEnabled = true
)
Updating user data
The SDK allows you to update profile and preference data for the logged in user via the updateUser method. This is the user as identified by the auth token provided by the authentication callback.
Setting up profile fields
For simple setup, create a UserSettings object and set some profile fields, then call the updateUser method on AACCore.shared.
The following optional profile fields can be supplied to update the data for the user. Any fields which have not been supplied will remain unmodified after the user update.
externalId: An optional string that represents an external identifier for the user.name: An optional string that represents the name of the user.email: An optional string that represents the email address of the user.phone: An optional string that represents the phone number of the user.city: An optional string that represents the city of the user.country: An optional string that represents the country of the user.region: An optional string that represents the region of the user.
The following code snippet shows how to set up some profile fields.
val settings = UserSettings()
// Set up some basic profile fields.
settings.externalId = "an external ID"
settings.name = "John Smith"
AACCore.shared.updateUser(settings)
Setting up custom profile fields
You can also set up custom fields in the user profile. Custom fields must first be created in Atomic Workbench before updating them. For more details on custom fields, see Custom Fields.
Custom fields are set using the setCustomField method, which accepts a value (as a string) and a key identifying the field.
Use the name property in the Workbench to identify a custom field, not the label property.
The following code snippet shows how to set custom fields.
settings.setCustomField("2025-01-15", "custom_date_field")
settings.setCustomField("some custom text", "custom_text_field")
Setting up notification preferences
You can use the following optional properties and methods to update the notification preferences for the user. Again, any fields which have not been supplied will remain unmodified after the user update.
notificationsEnabled: An optional boolean to determine whether notifications are enabled. The default notification setting of a user istrue.setNotificationTimeframes: A method that defines the notification time preferences of the user for different days of the week. If you specifyNotificationDay.Defaultto thedayparameter, the notification time preferences will be applied to every day.
Each day accepts a list of notification time periods, which are periods during which notifications are allowed. If an empty list is provided, notifications will be disabled for that day.
The following code snippet shows how to set up notification periods between 8am - 5:30pm & 7pm - 10pm on Monday.
val settings = UserSettings()
val timeframes = listOf(
NotificationTimeframe(
startHour = 8,
startMinute = 0,
endHour = 17,
endMinute = 30
),
NotificationTimeframe(
startHour = 19,
startMinute = 0,
endHour = 22,
endMinute = 0
)
)
settings.setNotificationTimeframes(timeframes, NotificationDay.Monday)
Hours are in the 24-hour format and must be between 0 & 23 inclusive, while minutes are values between 0 & 59 inclusive.
UpdateUser method: complete example
The following code snippet shows an example of using the updateUser method to update profile fields, custom profile fields and notification preferences.
val settings = UserSettings()
settings.externalId = "123"
settings.name = "User Name"
settings.email = "email@example.com"
settings.phone = "(+64)210001234"
settings.city = "Wellington"
settings.country = "New Zealand"
settings.region = "Wellington"
// Any further custom fields that have already been defined in the Workbench.
settings.setCustomField("2025-01-15", "myCustomDateField")
settings.setCustomField("My custom value", "myCustomTextField")
settings.notificationsEnabled = true
// Set up notification timeframes from 08:00 to 17:45, on all work days.
settings.setNotificationTimeframes(
listOf(
NotificationTimeframe(
startHour = 8,
startMinute = 0,
endHour = 17,
endMinute = 45
)
),
NotificationDay.Default
)
settings.setNotificationTimeframes(emptyList(), NotificationDay.Saturday)
settings.setNotificationTimeframes(emptyList(), NotificationDay.Sunday)
AACCore.shared.updateUser(settings) { result ->
when (result) {
is UpdateUserResult.Success -> {
Log.d("UpdateUser", "User updated successfully")
}
is UpdateUserResult.Failure -> {
Log.e("UpdateUser", "Failed to update user: ${result.message}")
}
}
}
Though all fields of UserSettings are optional, you must supply at least one field when calling updateUser.
Error handling
The updateUser method's callback receives an UpdateUserResult, which is a sealed class with two possible outcomes:
UpdateUserResult.Success: Indicates that the update was accepted by the platform.UpdateUserResult.Failure: Indicates that the update failed. Themessageproperty provides the reason for the failure.
Utility methods
Debug logging
Enable debug logging to view verbose logs of activity within the SDK, printed to the device’s console. Debug logging is disabled by default, and should not be enabled in release builds.
AACCore.shared.enableDebugMode(level: Int)
The parameter level is an integer that indicates the verbosity level of the logs exposed:
- Level 0: Default, no logs exposed
- Level 1: Error logs.
- Level 2: Warning logs.
- Level 3: Info logs.
- Level 4: Debug developer logs
- Level 5: Verbose developer logs and HTTP requests.
Setting the version of the client app
The SDK provides a method setClientAppVersion for setting the current version of your app. An app version is used with analytics to make it easy to track issues between app versions and general analytics around versions.
Version strings longer than 128 characters will be trimmed to that length.
The client app version defaults to unknown if you do not call this method.
The following code snippet sets the client app's version to Version 14.2 (14C18).
AACCore.shared.setClientAppVersion("Version 14.2 (14C18)")
Dependencies
Version 25.3.0 uses these top-level runtime dependencies.
- org.jetbrains.kotlin:kotlin-stdlib:1.9.24 -> 2.0.0
- io.atomic.actioncards:aaccore:25.3.0
- androidx.startup:startup-runtime:1.1.1
- androidx.datastore:datastore-preferences:1.1.1
- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10
- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3 -> 1.8.1
- io.ktor:ktor-client-cio:2.3.11
- org.slf4j:slf4j-android:1.7.32
- org.jetbrains.kotlin:kotlin-stdlib-common:1.9.10 -> 2.0.0
- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3 -> 1.8.1 (*)
- io.ktor:ktor-client-core:2.3.11 (*)
- io.ktor:ktor-client-content-negotiation:2.3.11
- io.ktor:ktor-client-serialization:2.3.11
- io.ktor:ktor-client-logging:2.3.11
- io.ktor:ktor-serialization-kotlinx-json:2.3.11
- io.ktor:ktor-client-websockets:2.3.11
- org.jetbrains.kotlinx:kotlinx-datetime:0.4.0
- com.benasher44:uuid:0.8.1
- org.jetbrains:markdown:0.3.1
- androidx.core:core-ktx:1.15.0
- androidx.lifecycle:lifecycle-runtime-ktx:2.8.7
- androidx.activity:activity-compose:1.10.0
- androidx.compose:compose-bom:2025.01.00
- androidx.compose.ui:ui -> 1.7.6 (*)
- androidx.compose.ui:ui-graphics -> 1.7.6 (*)
- androidx.compose.ui:ui-tooling-preview -> 1.7.6
- androidx.compose.material3:material3 -> 1.3.1
- androidx.navigation:navigation-compose:2.8.5
- androidx.constraintlayout:constraintlayout-compose:1.1.0
- androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7 (*)
- org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3 (*)
- androidx.compose.foundation:foundation:1.7.6 (*)
- io.coil-kt:coil:2.7.0
- io.coil-kt:coil-compose:2.7.0
- io.coil-kt:coil-svg:2.7.0
- com.caverock:androidsvg-aar:1.4
- androidx.media3:media3-exoplayer:1.8.0
- androidx.media3:media3-exoplayer-dash:1.8.0
- androidx.media3:media3-ui:1.8.0
- androidx.media3:media3-ui-compose:1.8.0