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()
            }
        }
    }
}

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:)