diff --git a/intercom_flutter/android/src/main/kotlin/io/maido/intercom/IntercomFlutterPlugin.kt b/intercom_flutter/android/src/main/kotlin/io/maido/intercom/IntercomFlutterPlugin.kt index a233b99..ea548d5 100644 --- a/intercom_flutter/android/src/main/kotlin/io/maido/intercom/IntercomFlutterPlugin.kt +++ b/intercom_flutter/android/src/main/kotlin/io/maido/intercom/IntercomFlutterPlugin.kt @@ -1,6 +1,8 @@ package io.maido.intercom import android.app.Application +import android.os.Handler +import android.os.Looper import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.activity.ActivityAware import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding @@ -13,6 +15,7 @@ import io.intercom.android.sdk.* import io.intercom.android.sdk.identity.Registration import io.intercom.android.sdk.push.IntercomPushClient import io.intercom.android.sdk.ui.theme.ThemeMode +import java.util.concurrent.CountDownLatch // No-op stream handler for windowDidHide event since it's only supported on iOS. class WindowDidHideStreamHandler : EventChannel.StreamHandler { @@ -25,9 +28,45 @@ class IntercomFlutterPlugin : FlutterPlugin, MethodCallHandler, EventChannel.Str @JvmStatic lateinit var application: Application + // Intercom.initialize does blocking Android Keystore operations internally (via runBlocking). + // Calling it on the main thread causes ANRs. The latch lets method calls that arrive before + // initialization finishes wait on a background thread instead of blocking the main thread. + private val initLatch = CountDownLatch(1) + private val isInitialized get() = initLatch.count == 0L + @JvmStatic fun initSdk(application: Application, appId: String, androidApiKey: String) { - Intercom.initialize(application, apiKey = androidApiKey, appId = appId) + Thread({ + Intercom.initialize(application, apiKey = androidApiKey, appId = appId) + initLatch.countDown() + }, "intercom-init").apply { + isDaemon = true + start() + } + } + + // Runs [block] on the main thread, waiting for init on a background thread if needed. + // Fast-path: if already initialized, runs [block] immediately with no extra thread. + internal fun runAfterInit(result: Result, block: () -> Unit) { + if (isInitialized) { + try { + block() + } catch (e: Exception) { + result.error("INTERCOM_ERROR", e.message, null) + } + return + } + val mainHandler = Handler(Looper.getMainLooper()) + Thread { + initLatch.await() + mainHandler.post { + try { + block() + } catch (e: Exception) { + result.error("INTERCOM_ERROR", e.message, null) + } + } + }.start() } } @@ -50,6 +89,10 @@ class IntercomFlutterPlugin : FlutterPlugin, MethodCallHandler, EventChannel.Str } override fun onMethodCall(call: MethodCall, result: Result) { + runAfterInit(result) { dispatch(call, result) } + } + + private fun dispatch(call: MethodCall, result: Result) { when (call.method) { "initialize" -> { val apiKey = call.argument("androidApiKey") @@ -206,7 +249,6 @@ class IntercomFlutterPlugin : FlutterPlugin, MethodCallHandler, EventChannel.Str val token = call.argument("token") if (token != null) { intercomPushClient.sendTokenToIntercom(application, token) - result.success("Token sent to Intercom") } } diff --git a/intercom_flutter/lib/intercom_flutter.dart b/intercom_flutter/lib/intercom_flutter.dart index af18f85..8787bc0 100755 --- a/intercom_flutter/lib/intercom_flutter.dart +++ b/intercom_flutter/lib/intercom_flutter.dart @@ -21,6 +21,15 @@ class Intercom { /// get the instance of the [Intercom]. static Intercom get instance => _instance; + /// Completes when [initialize] finishes. + /// + /// On Android, initialization runs off the main thread to avoid ANRs caused + /// by blocking Keystore operations inside the Intercom SDK. Await this future + /// if you need to guarantee the SDK is ready before calling other methods, + /// although the plugin itself queues calls automatically while init is pending. + static Future get initialized => _initCompleter.future; + static final Completer _initCompleter = Completer(); + /// Function to initialize the Intercom SDK. /// /// First, you'll need to get your Intercom [appId]. @@ -36,9 +45,10 @@ class Intercom { String appId, { String? androidApiKey, String? iosApiKey, - }) { - return IntercomFlutterPlatform.instance + }) async { + await IntercomFlutterPlatform.instance .initialize(appId, androidApiKey: androidApiKey, iosApiKey: iosApiKey); + if (!_initCompleter.isCompleted) _initCompleter.complete(); } /// You can check how many unread conversations a user has