Syncing deviceUUID Between Android Mobile SDK and JS SDK in a WebView App
What this step achievesA single customer profile is created in Maestra, capturing events from both the mobile SDK and the JS SDK.
If your app uses WebView, it may also run the website's JS tracker. This means that when the mobile app is installed, Maestra will create two separate customer profiles for the same user — one from the JS SDK and one from the mobile SDK, each with a different deviceUUID.
To avoid duplicate profiles and keep all data in a single customer record, follow the steps below.
Step 1: Add a method to retrieve deviceUUID
deviceUUIDcompanion object {
// Set your preferred timeout for waiting on deviceUUID.
// If deviceUUID is not received on first init,
// sync will happen on the next page load / app launch.
// If you're using third-party cookies, you must wait for deviceUUID —
// otherwise sync will not occur.
const val FETCHING_DEVICE_UUID_TIMEOUT = 5000L // in milliseconds
}
// Waits for deviceUUID for up to FETCHING_DEVICE_UUID_TIMEOUT ms
private suspend fun getDeviceUUID(): String = withTimeout(FETCHING_DEVICE_UUID_TIMEOUT) {
suspendCancellableCoroutine { continuation ->
Mindbox.subscribeDeviceUuid { uuid ->
if (uuid.isNotEmpty()) {
continuation.resume(uuid)
} else {
continuation.resumeWithException(Exception("Device UUID is empty"))
}
}
}
}
Timing expectationsOn the first app launch,
deviceUUIDmay take a few seconds to be returned by the provider.
On subsequent launches, it is typically available within 100–200 ms.
Third-party cookiesBy default, third-party cookies are disabled starting from Android API 21. Make sure you are not calling
CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true)unless your implementation requires it.If you are using third-party cookies, you must wait for
deviceUUIDon the first initialization — otherwise synchronization will not happen.
Step 2: Wait for deviceUUID before loading the page
deviceUUID before loading the pageAdd the following to your MainActivity.onCreate:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Initialize your WebView and other components
// Launch a coroutine to wait for deviceUUID before loading the page
CoroutineScope(Dispatchers.Main).launch {
try {
// Fetch deviceUUID without blocking the main thread
deviceUUID = withContext(Dispatchers.IO) { getDeviceUUID() }
Mindbox.writeLog("DeviceUUID for sync received: $deviceUUID", logLevel = Level.DEBUG)
webView.loadUrl(URL)
} catch (e: TimeoutCancellationException) {
Mindbox.writeLog("Timeout waiting for deviceUUID. Loading without UUID", logLevel = Level.DEBUG)
webView.loadUrl(URL)
} catch (e: Exception) {
Mindbox.writeLog("Failed to get deviceUUID: ${e.message}", logLevel = Level.ERROR)
webView.loadUrl(URL)
}
}
// Rest of your onCreate code
}Step 3: Sharing deviceUUID Between Android Mobile SDK and JS SDK
This method passes deviceUUID to the web page via cookies and localStorage:
private fun syncMindboxDeviceUUIDs(uuid: String) {
webView.evaluateJavascript(
"""
document.cookie = "mindboxDeviceUUID=$uuid";
window.localStorage.setItem('mindboxDeviceUUID', '$uuid');
"""
) {
Mindbox.writeLog("deviceUUID synced: $uuid", logLevel = Level.DEBUG)
}
}Step 4: Call syncMindboxDeviceUUIDs in onPageStartedcallback
syncMindboxDeviceUUIDs in onPageStartedcallbackprivate val webViewClientInstance: WebViewClient by lazy {
object : WebViewClient() {
override fun onPageStarted(view: WebView, url: String?, favicon: android.graphics.Bitmap?) {
super.onPageStarted(view, url, favicon)
Log.d(Utils.TAG, "Page started loading: $url")
// Sync deviceUUID
deviceUUID?.let {
syncMindboxDeviceUUIDs(it)
} ?: run {
Mindbox.subscribeDeviceUuid { uuid ->
if (uuid.isNotEmpty()) {
deviceUUID = uuid
syncMindboxDeviceUUIDs(uuid)
}
}
}
}
}
}Full example
<details>
<summary>MainActivity.kt — complete implementation</summary>
```kotlin
package com.mindbox.example
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.webkit.*
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import cloud.mindbox.mobile_sdk.Mindbox
import cloud.mindbox.mobile_sdk.logger.Level
import com.mindbox.example.databinding.ActivityMainBinding
import kotlinx.coroutines.*
import kotlin.coroutines.*
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
class MainActivity : AppCompatActivity() {
companion object {
const val URL = "https://your-website.com/"
const val FETCHING_DEVICE_UUID_TIMEOUT = 4000L
}
private lateinit var webView: WebView
private var deviceUUID: String? = null
private var _binding: ActivityMainBinding? = null
private val binding: ActivityMainBinding get() = _binding!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// Enable WebView debugging
WebView.setWebContentsDebuggingEnabled(true)
// Initialize WebView after Mindbox.init if init is called in the activity
webView = binding.webView.apply {
settings.javaScriptEnabled = true
settings.domStorageEnabled = true
webViewClient = webViewClientInstance
}
/***
* Start loading the page after obtaining the deviceUUID.
* On the first app launch, obtaining the deviceUUID may take several seconds.
* If you don't wait for the deviceUUID, synchronization will occur on the next page load.
* The waiting time can be adjusted in the FETCHING_DEVICE_UUID_TIMEOUT constant.
***/
CoroutineScope(Dispatchers.Main).launch {
try {
deviceUUID = withContext(Dispatchers.IO) { getDeviceUUID() }
Mindbox.writeLog("DeviceUUID for synchronization received: $deviceUUID", logLevel = Level.DEBUG)
webView.loadUrl(URL)
} catch (e: TimeoutCancellationException) {
Mindbox.writeLog("Timeout while waiting for Device UUID. Loading without UUID", logLevel = Level.DEBUG)
webView.loadUrl(URL)
} catch (e: Exception) {
Mindbox.writeLog("Failed to get Device UUID for synchronization: ${e.message}", logLevel = Level.ERROR)
webView.loadUrl(URL)
}
}
binding.viewCookiesButton.setOnClickListener { showCookies() }
processMindboxIntent(intent = intent, context = this)?.let { (url, payload) ->
Log.d(Utils.TAG, "Data from push: url: $url, payload: $payload")
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
processMindboxIntent(intent = intent, context = this)?.let { (url, payload) ->
Log.d(Utils.TAG, "Data from push: url: $url, payload: $payload")
}
Mindbox.onNewIntent(intent)
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
private val webViewClientInstance: WebViewClient by lazy {
object : WebViewClient() {
override fun onPageStarted(view: WebView, url: String?, favicon: android.graphics.Bitmap?) {
super.onPageStarted(view, url, favicon)
Log.d(Utils.TAG, "Page started loading: $url")
deviceUUID?.let {
syncMindboxDeviceUUIDs(it)
} ?: run {
Mindbox.subscribeDeviceUuid { uuid ->
if (uuid.isNotEmpty()) {
deviceUUID = uuid
syncMindboxDeviceUUIDs(uuid)
}
}
}
}
}
}
private suspend fun getDeviceUUID(): String = withTimeout(FETCHING_DEVICE_UUID_TIMEOUT) {
suspendCancellableCoroutine { continuation ->
Mindbox.subscribeDeviceUuid { uuid ->
if (uuid.isNotEmpty()) {
continuation.resume(uuid)
} else {
continuation.resumeWithException(Exception("Device UUID is empty"))
}
}
}
}
private fun syncMindboxDeviceUUIDs(uuid: String) {
webView.evaluateJavascript(
"""
document.cookie = "mindboxDeviceUUID=$uuid";
window.localStorage.setItem('mindboxDeviceUUID', '$uuid');
"""
) {
Mindbox.writeLog("Device UUID synchronized: $uuid", logLevel = Level.DEBUG)
}
}
private fun showCookies() {
val cookies = CookieManager.getInstance().getCookie(URL)
Log.d(Utils.TAG, "Cookies: $cookies")
Mindbox.subscribeDeviceUuid { uuid ->
Log.d(Utils.TAG, "mobile sdk deviceUUID=$uuid")
}
webView.evaluateJavascript(
"(function() {return window.localStorage.getItem('mindboxDeviceUUID')})()"
) { result ->
Log.d(Utils.TAG, "js sdk deviceUUID: $result")
}
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun clearAllCookies() {
WebStorage.getInstance().deleteAllData()
val cookieManager = CookieManager.getInstance()
cookieManager.removeAllCookies { success ->
if (success) {
Log.d(Utils.TAG, "All cookies cleared")
} else {
Log.e(Utils.TAG, "Failed to clear cookies")
}
}
}
}
```
</details>Debugging
Use the method below to verify sync is working correctly. It logs three values that should all match: the mobile SDK deviceUUID, the value stored in cookies, and the value stored in localStorage.
Run this after the JS tracker has initialized:
private fun showCookies() {
val cookies = CookieManager.getInstance().getCookie(URL)
Log.d(Utils.TAG, "Cookies: $cookies")
Mindbox.subscribeDeviceUuid { uuid ->
Log.d(Utils.TAG, "mobile sdk deviceUUID=$uuid")
}
webView.evaluateJavascript(
"(function() { return window.localStorage.getItem('mindboxDeviceUUID'); })();"
) { result ->
Log.d(Utils.TAG, "js sdk deviceUUID: $result")
}
}Updated 6 months ago

