NeoDay Android SDK
The NeoDay SDK provides a convenient way of creating the screens for components like campaigns and coupons that are set up in your NeoDay backoffice.
Setting up NeoDaySDK AAR file
SDK 2.0.0
- Minimum Android API (minSdk): 21
- Target Android API (targetSdk): 35
- Kotlin version: 2.1.0
- Kotlin Compiler Extension Version: 2.1.0
- Android Gradle plugin version: 8.8.0
- Gradle version: 8.10.2
- Java version: 21
SDK 1.16.0
- Minimum Android API (minSdk): 21
- Target Android API (targetSdk): 35
- Kotlin version: 1.8.22
- Kotlin Compiler Extension Version: 1.4.8
- Android Gradle plugin version: 8.1.1
- Gradle version: 8.9
- Java version: 21
SDK 1.15.37 and below
- Minimum Android API (minSdk): 21
- Target Android API (targetSdk): 33
- Kotlin version: 1.7.10
- Kotlin Compiler Extension Version: 1.3.1
- Android Gradle plugin version: 7.2.2
- Gradle version: 7.3.3
- Java version: 11
Create a libs
directory within the directory of your app module if it doesn't already exist.
Example:Β
<project-name>/<app-name>/libs
Place the NeoDaySDK-x.x.x.aarΒ in theΒ libs
directory
Preparing your moduleΒ build.gradle file
Add the kotlin-parcelize plugin to the plugins block.
plugins {
// ...
id 'kotlin-parcelize'
// ...
}
Enable viewBinding and compose in the buildFeatures block
buildFeatures {
viewBinding true
compose true
}
Add these libraries required by NeoDaySDK to your dependencies
implementation fileTree(dir: "libs", include: ["NeoDaySDK-x.x.x.aar"])
// OR
implementation files("libs/NeoDaySDK-x.x.x.aar")
For the required 3rd party dependencies please see 3rd Party dependencies section.
Setting up manifest FileProvider requirement
In order to have functional file access from SDK, client app should set up FileProvider entry in application manifest file, under application tag.
<provider
android:authorities="${applicationId}.NeoDayFileProvider"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/path_provider" />
</provider>
Authority should be defined by client app (above is just an example) and provided as one of the configuration parameters for the SDK. XML file that is defining available paths for provider (path_provider in the example), should also contain external cache entry, so SDK can reference files from it.
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-cache-path name="captured_images" path="/"/>
</paths>
Implementing the NeoDay SDK
Preferably your application should have subclass of theΒ android.app.Application where you can setup and keep the instance of the NeoDaySDK.
SDK version: 1.2.0
class ExampleApplication: Application() {
// You may also use mutableStateOf
val sdkIsInitialized = MutableLiveData(false)
lateinit var neoDaySDK: NeoDaySDK
private set
override fun onCreate() {
super.onCreate()
instance = this
setupNeoDaySDK()
}
private fun setupNeoDaySDK() {
// Declare the NeoDaySDK instance
val gatewayConfig = GatewayConfig(
gateway = Uri.parse("<url>"),
clientWebURL = Uri.parse("<url>"),
clientID = "<client_id>",
xSiteToken = "<site_token>",
siteStyleID = "<sitestyle_id>"
)
val config = NeoDaySDKConfig(
preferredDateFormat = "<date-format>",
fileProviderAuthority = "<file-provider>", //example: $packageName.NeoDayFileProvider
webViewTimeoutDuration = 10_000, // Calculated in milliseconds, default: to 10_000
gatewayConfig = gatewayConfig,
campaignConfig = CampaignConfig(
placeholderImage = <DrawableRes>,
transactionsConfig = CampaignConfig.TransactionsConfig(
headerImage = <DrawableRes>,
placeholderImage = <DrawableRes>
)
)
)
neoDaySDK = NeoDaySDK(
config = config,
context = <application context>
)
// Initialize the SDK by calling one of the initialize methods
val endpoint = CustomerAuthenticationEndpoint(
url = Uri.parse("<url>"),
headers = null,
parameters = null
)
neoDaySDK.initialize(endpoint) { result ->
sdkIsInitialized.postValue(result.isSuccess)
}
}
}
SDK version: 1.3.0
class ExampleApplication: Application() {
// You may also use mutableStateOf
val sdkIsInitialized = MutableLiveData(false)
lateinit var neoDaySDK: NeoDaySDK
private set
override fun onCreate() {
super.onCreate()
instance = this
setupNeoDaySDK()
}
private fun setupNeoDaySDK() {
// Declare the NeoDaySDK instance
val gatewayConfig = GatewayConfig(
gateway = Uri.parse("<url>"),
clientWebURL = Uri.parse("<url>"),
clientID = "<client_id>",
xSiteToken = "<site_token>",
siteStyleID = "<sitestyle_id>"
)
val config = NeoDaySDKConfig(
preferredLocale = NeoDayLocale("en", "US"),
preferredDateFormat = "<date-format>",
fileProviderAuthority = "<file-provider>", //example: $packageName.NeoDayFileProvider
webViewTimeoutDuration = 10_000, // Calculated in milliseconds, default: to 10_000
gatewayConfig = gatewayConfig,
campaignConfig = CampaignConfig(
placeholderImage = <DrawableRes>,
transactionsConfig = CampaignConfig.TransactionsConfig(
headerImage = <DrawableRes>,
placeholderImage = <DrawableRes>
)
)
)
neoDaySDK = NeoDaySDK(
config = config,
context = <application context>
)
// Initialize the SDK by calling one of the initialize methods
val endpoint = CustomerAuthenticationEndpoint(
url = Uri.parse("<url>"),
headers = null,
parameters = null
)
neoDaySDK.authenticateUser(endpoint) { result ->
sdkIsInitialized.postValue(result.isSuccess)
}
}
}
SDK version: 1.3.1
class ExampleApplication: Application() {
// ...
private fun setupNeoDaySDK() {
// ...
val config = NeoDaySDKConfig(
preferredLocale = NeoDayLocale("en", "US"),
preferredDateFormat = "<date-format>",
fileProviderAuthority = "<file-provider>", //example: $packageName.NeoDayFileProvider
webViewTimeoutDuration = 10_000, // Calculated in milliseconds, default: to 10_000
gatewayConfig = gatewayConfig,
campaignConfig = CampaignConfig(
placeholderImage = <DrawableRes>,
transactionsConfig = CampaignConfig.TransactionsConfig(
headerImage = <DrawableRes>,
placeholderImage = <DrawableRes>
)
)
)
// ...
}
// ...
}
Requesting Screen/View from NeoDaySDK
NeoDaySDK view methods are executed asynchronously, you have to use the completion callback to get the result.
Using Composable method
Column {
val vc = remember { mutableStateOf<NeoDayViewController?>(null) }
LaunchedEffect(Unit) {
neoDaySDK.makeCampaignsOverview { result ->
try {
vc.value = result.getOrThrow()
vc.value?.fragmentManager = fragmentManager
vc.value?.showBackButton = false
} catch (ex: Exception) {
ex.printStackTrace()
// additional error handler
}
}
}
vc.value?.getView()?.invoke()
}
Using ComposeView in XML layout and Fragment
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
class ExampleFragment : Fragment() {
private var _binding: FragmentExampleBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentExampleBinding.inflate(inflater, container, false)
val exampleApp = requireActivity().application as ExampleApplication
val view = binding.root
val composeView: ComposeView = binding.composeView
exampleApp.sdkIsInitialized.observe(viewLifecycleOwner) { sdkIsInitialized ->
if (sdkIsInitialized) {
exampleApp.neoDaySDK.makeCampaignsOverview { result ->
try {
val vc = result.getOrThrow()
composeView.apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
MaterialTheme {
vc.showBackButton = false
vc.getView().invoke()
}
}
}
} catch (ex: Exception) {
ex.printStackTrace()
}
}
}
}
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Using ComposeView directly within Fragment
class ExampleFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val exampleApp = requireActivity().application as ExampleApplication
return ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
MaterialTheme {
val vc = remember { mutableStateOf<NeoDayViewController?>(null) }
exampleApp.sdkIsInitialized.observe(viewLifecycleOwner) { sdkIsInitialized ->
if (sdkIsInitialized) {
exampleApp.neoDaySDK.makeCampaignsOverview { result ->
try {
vc.value = result.getOrThrow()
vc.value?.showBackButton = false
} catch (ex: Exception) {
ex.printStackTrace()
}
}
}
}
vc.value?.getView()?.invoke()
}
}
}
}
}
Using Activity.setContent
class ExampleActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val exampleApp = requireActivity().application as ExampleApplication
setContent { // In here, we can call composables!
MaterialTheme {
val vc = remember { mutableStateOf<NeoDayViewController?>(null) }
exampleApp.sdkIsInitialized.observe(viewLifecycleOwner) { sdkIsInitialized ->
if (sdkIsInitialized) {
exampleApp.neoDaySDK.makeCampaignsOverview { result ->
try {
vc.value = result.getOrThrow()
vc.value?.showBackButton = false
} catch (ex: Exception) {
ex.printStackTrace()
}
}
}
}
vc.value?.getView()?.invoke()
}
}
}
}
Handling DeepLink views with NeoDayDeepLinkListener
Sometimes the SDK need to display some screens, example when a user taps one of the campaigns on the campaign overview screen the SDK will invoke the NeoDayDeepLinkListener that returns the campaign detail screen.
class ExampleActivity : AppCompatActivity() {
private val viewModel: ExampleActivityViewModel by viewModels { ExampleActivityViewModelFactory() }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Set the Feedback listener
exampleApp.neoDaySDK.neoDayDeepLinkListener = object : NeoDayDeepLinkListener {
override fun handleDeepLink(
deepLink: DeepLink,
factoryMethod: ((Result<NeoDayViewController>) -> Unit) -> Unit
) {
factoryMethod.invoke { result ->
try {
val vc = result.getOrThrow()
viewModel.neoDayViewControllers[deepLink.identifier.rawValue] = vc
viewModel.showViewController.value = deepLink.identifier.rawValue
} catch (ex: Exception) {
ex.printStackTrace()
}
}
}
}
setContent {
MaterialTheme {
val navHostController = rememberNavController()
if(viewModel.showViewController.value.isNullOrBlank().not()) {
val identifier = viewModel.showViewController.value!!
viewModel.showViewController.value = null
navHostController.navigate("deeplink/$identifier")
}
Navigation(
navHostController = navHostController,
viewModel = viewModel,
modifier = Modifier.fillMaxSize()
)
}
}
}
}
@Composable
fun Navigation(
navHostController: NavHostController,
viewModel: MainActivityViewModel,
modifier: Modifier = Modifier
) {
NavHost(
navController = navHostController,
startDestination = "<start destination>",
modifier = modifier
) {
// ...
composable("deeplink/{identifier}") {
val identifier = it.arguments?.getString("identifier") ?: run {
Text("Error: identifier was not set.")
return@composable
}
viewModel.neoDayViewControllers[identifier]?.let { vc ->
vc.backButtonListener = { navHostController.navigateUp() }
vc.getView().invoke()
}
DisposableEffect(key) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_DESTROY) {
viewModel.neoDayViewControllers.remove(identifier)
}
}
lifecycle.addObserver(observer)
onDispose {
lifecycle.removeObserver(observer)
}
}
}
// ...
}
}
How to handle when the SDK requires re-authentication with NeedToAuthenticateListener
When the SDK can no longer refresh the token it will invoke the NeedToAuthenticateDelegate so your application can reinitialize the SDK.
SDK version: 1.2.0
class ExampleApplication: Application() {
// ...
fun initializeSDK() {
// Initialize the SDK by calling one of the initialize methods
val endpoint = CustomerAuthenticationEndpoint(
url = Uri.parse("<url>"),
headers = null,
parameters = null
)
neoDaySDK.initialize(endpoint) { result ->
sdkIsInitialized.postValue(result.isSuccess)
}
}
// ...
}
SDK version: 1.3.0
class ExampleApplication: Application() {
// ...
fun initializeSDK() {
// Initialize the SDK by calling one of the initialize methods
val endpoint = CustomerAuthenticationEndpoint(
url = Uri.parse("<url>"),
headers = null,
parameters = null
)
neoDaySDK.authenticateUser(endpoint) { result ->
sdkIsInitialized.postValue(result.isSuccess)
}
}
// ...
}
Setting the NeedToAuthenticateListener
exampleApp.neoDaySDK.needToAuthenticateListener = object : NeedToAuthenticateListener {
override fun needToAuthenticate() {
exampleApp.initializeSDK()
}
}
How to handle when the SDK is processing data
When the SDK is fetching from or submitting data to the NeodDay Gateway, it will invoke the SDKDataLoadingListener to notify when data is being loaded or not. That way you know when to show or hide a loading indicator while data is being processed. It's important to set this listener when using the NeoDay SDK.
SDK version: 1.2.0, 1.3.0
exampleApp.neoDaySDK.dataLoadingListener = object : SDKDataLoadingListener {
override fun isBusyLoading(value: Boolean) {
viewModel.showLoadingAnimation.value = value
}
}
Setting overview screen title
NeoDayViewController.setTitle() allows you to specify custom screen title. If the value passed is null the SDK will use the default title screen, if the value is empty string the SDK will hide the screen title.
Only overview screens support NeoDayViewController.setTitle() other screens may ignore this method even if it been called.
exampleApp.neoDaySDK.makeCampaignsOverview { result ->
try {
val vc = result.getOrThrow()
composeView.apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
MaterialTheme {
vc.setTitle("Overview Screen")
vc.showBackButton = false
vc.getView().invoke()
}
}
}
} catch (ex: Exception) {
ex.printStackTrace()
}
}
Classes
SDK version: 1.2.0
/**
* Configures the NeoDay SDK in order to communicate with the Backoffice
*
* @param gateway the URL used to communicate with the backend
* @param clientWebURL the URL of the NeoDay web client
* @param xSiteToken Used for identification
* @param clientId used to identify the client
* @param siteStyleId the id used to get SiteStyle that is configured in the Backoffice
* @param userSettingsId the id used to get the data for User Settings
*/
data class GatewayConfig(
val gateway: Uri,
val clientWebURL: Uri,
val clientId: String,
val xSiteToken: String,
val siteStyleId: String
)
data class CampaignConfig(
@DrawableRes val placeholderImage: Int? = null,
@DrawableRes val noDataImage: Int? = null,
val transactionsConfig: TransactionsConfig
) {
data class TransactionsConfig(
@DrawableRes val headerImage: Int,
@AnyRes val headerPrefix: Int? = null,
@AnyRes val headerSuffix: Int? = null,
val limit: CampaignTransactionHistoryLimit = CampaignTransactionHistoryLimit.ShowAll
)
}
data class CouponConfig(
@DrawableRes val placeholderImage: Int? = null,
@DrawableRes val noDataImage: Int? = null
)
data class NeoDaySDKConfig(
val preferredDateFormat: String,
val fileProviderAuthority: String,
val webViewTimeoutDuration: Long = 10_000,
val gatewayConfig: GatewayConfig,
val campaignConfig: CampaignConfig,
val couponConfig: CouponConfig? = null
)
data class AuthenticationToken(
val accessToken: String,
val accessTokenExpirationDate: DateTime,
val refreshToken: String
)
class CustomerAuthenticationEndpoint(
val baseURL: Uri,
val headers: HashMap<String, String>?,
val parameters: HashMap<String, Any>?,
val responseTokenFieldName: String = "token"
)
interface NeoDayViewController {
var backButtonListener: (() -> Unit)?
var showBackButton: Boolean
var fragmentManager: FragmentManager?
@Throws(Exception::class)
fun getView(): @Composable () -> Unit
// Allows setting a custom screen title. If value passed in null the SDK will use the default screen title.
// If value is an empty string the SDK will omit the screen title.
// Currently only overview screen supports setTitle, other screens will ignore this even if this method was called.
fun setTitle(title: String?)
}
class NeoDaySDK(
val config: NeoDaySDKConfig,
private val context: Context,
private val dispatcher: CoroutineDispatcher = Dispatchers.IO
) {
lateinit var siteStyle: SiteStyle
private set
// A listener that will be called when a Feedback needs to be shown
var feedbackListener: FeedbackListener? = null
// A listener that will be called when a Survey needs to be shown
var surveyListener: SurveyListener? = null
// A listener for the client's application to handle the DeepLinks invokes
var neoDayDeepLinkListener: NeoDayDeepLinkListener? = null
// A listener that is invoked by the SDK when it needs to display a loading indicator
var dataLoadingListener: SDKDataLoadingListener? = null
// A listener that will be called by the SDK when it requires to be in login state
var needToAuthenticateListener: NeedToAuthenticateListener? = null
/**
* Initializes the NeoDay SDK by using a signed token for the bearing user.
*
* @param endPoint the endpoint that will return the signed token for the bearing user.
* @param completion will return an exception if an error occurred or null if initialization succeed.
*/
fun initialize(
authenticationEndpoint: CustomerAuthenticationEndpoint,
completion: (Throwable?) -> Void
) : Job
/**
* Initializes the NeoDay SDK by using an AuthenticationToken.
*
* @param token the AuthenticationToken that will be used for the user.
* @param completion will return an exception if an error occurred or null if initialization succeed.
*/
fun initialize(
token: AuthenticationToken, completion: (Throwable?) -> Unit
) : Job
/**
* Authenticates with the given token.
*
* @param token the AuthenticationToken to be used for authentication.
* @throws Exception an Error when called before initializing the NeoDay SDK.
*/
@Throws(java.lang.Exception::class)
fun authenticateWithToken(token: AuthenticationToken): Job {
/**
* Removes the AuthenticationToken stored by the NeoDay SDK.
*
*/
fun removeToken(): Job
/**
* Retrieves the AuthenticationToken that is used by the NeoDaySDK.
*
* @return AuthenticationToken that the NeoDay SDK uses or null.
*/
fun retrieveStoredToken(): Flow<AuthenticationToken?>
}
SDK version: 1.3.0
/**
* Configures the NeoDay SDK in order to communicate with the Backoffice
*
* @param gateway the URL used to communicate with the backend
* @param clientWebURL the URL of the NeoDay web client
* @param xSiteToken Used for identification
* @param clientId used to identify the client
* @param siteStyleId the id used to get SiteStyle that is configured in the Backoffice
* @param userSettingsId the id used to get the data for User Settings
*/
data class GatewayConfig(
val gateway: Uri,
val clientWebURL: Uri,
val clientId: String,
val xSiteToken: String,
val siteStyleId: String,
val userSettingsId: String
)
data class CampaignConfig(
@DrawableRes val placeholderImage: Int? = null,
@DrawableRes val noDataImage: Int? = null,
val transactionsConfig: TransactionsConfig,
val imageRecognitionConfig: ImageRecognitionConfig = ImageRecognitionConfig()
) {
data class TransactionsConfig(
@DrawableRes val headerImage: Int,
@AnyRes val headerPrefix: Int? = null,
@AnyRes val headerSuffix: Int? = null,
val limit: CampaignTransactionHistoryLimit = CampaignTransactionHistoryLimit.ShowAll
)
data class ImageRecognitionConfig(
val compressionQuality: NDImageCompressionQuality = NDImageCompressionQuality.NONE,
val scalingSize: NDImageScalingSize = NDImageScalingSize.None
)
}
data class CouponConfig(
@DrawableRes val placeholderImage: Int? = null,
@DrawableRes val noDataImage: Int? = null,
@DrawableRes val errorImage: Int? = null
)
data class NeoDaySDKConfig(
val preferredDateFormat: String,
val fileProviderAuthority: String,
val webViewTimeoutDuration: Long = 10_000,
val gatewayConfig: GatewayConfig,
val campaignConfig: CampaignConfig,
val couponConfig: CouponConfig? = null,
val loyaltyCardConfig: LoyaltyCardConfig? = null
)
data class AuthenticationToken(
val accessToken: String,
val accessTokenExpirationDate: DateTime,
val refreshToken: String
)
class CustomerAuthenticationEndpoint(
val baseURL: Uri,
val headers: HashMap<String, String>?,
val parameters: HashMap<String, Any>?,
val responseTokenFieldName: String = "token"
)
interface NeoDayViewController {
var id: Int
var backButtonListener: (() -> Unit)?
var showBackButton: Boolean
var fragmentManager: FragmentManager?
@Throws(Exception::class)
fun getView(): @Composable () -> Unit
// Allows setting a custom screen title. If value passed in null the SDK will use the default screen title.
// If value is an empty string the SDK will omit the screen title.
// Currently only overview screen supports setTitle, other screens will ignore this even if this method was called.
fun setTitle(title: String?)
}
class NeoDaySDK(
val config: NeoDaySDKConfig,
private val context: Context,
private val dispatcher: CoroutineDispatcher = Dispatchers.IO
) {
lateinit var siteStyle: SiteStyle
private set
// A listener that will be called when a Feedback needs to be shown
var feedbackListener: FeedbackListener? = null
// A listener that will be called when a Survey needs to be shown
var surveyListener: SurveyListener? = null
// A listener for the client's application to handle the DeepLinks invokes
var neoDayDeepLinkListener: NeoDayDeepLinkListener? = null
// A listener that is invoked by the SDK when it needs to display a loading indicator
var dataLoadingListener: SDKDataLoadingListener? = null
// A listener that will be called by the SDK when it requires to be in login state
var needToAuthenticateListener: NeedToAuthenticateListener? = null
/**
* Authenticate with NeoDay by using a signed token for the bearing user.
*
* @since 1.3.0
* @param authenticationEndpoint the endpoint that will return the signed token for the bearing user.
* @param completion will return an exception if an error occurred or null if authentication succeed.
*/
fun authenticateUser(
authenticationEndpoint: CustomerAuthenticationEndpoint,
completion: (Throwable?) -> Unit
) : Job
/**
* Authenticate with NeoDay by using an AuthenticationToken.
*
* @since 1.3.0
* @param token the AuthenticationToken that will be used for the user.
* @param completion will return an exception if an error occurred or null if authentication succeed.
*/
fun authenticateUser(
token: AuthenticationToken,
completion: (Throwable?) -> Unit
) : Job
/**
* Authenticate with NeoDay without using an authenticated user.
* In this state only the Campaigns and Reward shop are available
*
* @since 1.3.0
* @param completion will return an exception if an error occurred or null if authentication succeed.
*/
fun authenticate(
completion: ((Throwable?) -> Unit)
) : Job
/**
* Removes the AuthenticationToken stored by the NeoDay SDK.
*
*/
fun removeToken(): Job
/**
* Retrieves the AuthenticationToken that is used by the NeoDaySDK.
*
* @return AuthenticationToken that the NeoDay SDK uses or null.
*/
fun retrieveStoredToken(): Flow<AuthenticationToken?>
}
SDK version: 1.3.1
The preferredLocale
was added to the NeoDaySDKConfig
. The default value for this field is the current device's locale.
data class NeoDaySDKConfig(
val preferredLocale: NeoDayLocale,
val preferredDateFormat: String,
val fileProviderAuthority: String,
val webViewTimeoutDuration: Long = 10_000,
val gatewayConfig: GatewayConfig,
val campaignConfig: CampaignConfig,
val couponConfig: CouponConfig? = null,
val loyaltyCardConfig: LoyaltyCardConfig? = null
)
SDK version: 1.5.0
The NeoDaySDKDebugListener is added:
class NeoDaySDK {
...
var debugListener: NeoDaySDKDebugListener?
...
}
It can return the following types of debug messages related to Site Style colors or fonts:
When the Backoffice configured Site Style color, cannot be interpreted:
"SiteStyle mode cannot be interpreted, a 'clear' color is used instead."
SDK version: 1.7.0
The Campaign Counter is introduced on screens created by makeCampaignDetail(campaignId: campaignInstanceId: completion:)