Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 {
Expand All @@ -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()
}
}

Expand All @@ -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<String>("androidApiKey")
Expand Down Expand Up @@ -206,7 +249,6 @@ class IntercomFlutterPlugin : FlutterPlugin, MethodCallHandler, EventChannel.Str
val token = call.argument<String>("token")
if (token != null) {
intercomPushClient.sendTokenToIntercom(application, token)

result.success("Token sent to Intercom")
}
}
Expand Down
14 changes: 12 additions & 2 deletions intercom_flutter/lib/intercom_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> get initialized => _initCompleter.future;
static final Completer<void> _initCompleter = Completer<void>();

/// Function to initialize the Intercom SDK.
///
/// First, you'll need to get your Intercom [appId].
Expand All @@ -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
Expand Down