Skip to content
Open
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
57 changes: 55 additions & 2 deletions android/src/main/kotlin/com/truecallersdk/TruecallerSdkPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry
import java.util.Locale
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicBoolean

const val TAG = "TruecallerSdkPlugin"
const val INITIALIZE_SDK = "initializeSDK"
Expand Down Expand Up @@ -91,6 +94,11 @@ public class TruecallerSdkPlugin : FlutterPlugin, MethodCallHandler, EventChanne
private var binding: ActivityPluginBinding? = null
private var launcher: ActivityResultLauncher<Intent>? = null
private val gson = Gson()
private var ioExecutor: ExecutorService = Executors.newSingleThreadExecutor()
private val mainHandler = android.os.Handler(android.os.Looper.getMainLooper())
// Guards against posting result.success() after cleanUp() has detached the MethodChannel.
// Set to true when cleanUp() runs; checked before posting result back to main thread.
private val isCleanedUp = AtomicBoolean(false)

override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
onAttachedToEngine(flutterPluginBinding.binaryMessenger)
Expand All @@ -106,7 +114,36 @@ public class TruecallerSdkPlugin : FlutterPlugin, MethodCallHandler, EventChanne
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
when (call.method) {
INITIALIZE_SDK -> {
getTcSdkOptions(call)?.let { TcSdk.init(it) } ?: result.error(
getTcSdkOptions(call)?.let { options ->
// Run TcSdk.init() on a background thread to avoid ANR.
// During init, ContentResolver.query() to the Truecaller app's
// ContentProvider can block for seconds. Running on a background
// thread prevents main-thread ANR.
val resultConsumed = AtomicBoolean(false)
try {
ioExecutor.execute {
try {
TcSdk.init(options)
mainHandler.post {
if (!isCleanedUp.get() && resultConsumed.compareAndSet(false, true)) {
result.success(true)
}
}
} catch (e: Exception) {
android.util.Log.e(TAG, "TcSdk.init failed", e)
mainHandler.post {
if (!isCleanedUp.get() && resultConsumed.compareAndSet(false, true)) {
result.success(false)
}
}
}
}
} catch (e: java.util.concurrent.RejectedExecutionException) {
// Executor was shut down (rapid detach-reattach during config change)
android.util.Log.w(TAG, "Executor shut down, cannot init SDK", e)
result.success(false)
}
} ?: result.error(
"UNAVAILABLE",
"Activity not available.",
null
Expand Down Expand Up @@ -429,6 +466,11 @@ public class TruecallerSdkPlugin : FlutterPlugin, MethodCallHandler, EventChanne
}

override fun onAttachedToActivity(binding: ActivityPluginBinding) {
isCleanedUp.set(false)
// Recreate executor if it was shut down during a previous cleanUp()
if (ioExecutor.isShutdown) {
ioExecutor = Executors.newSingleThreadExecutor()
}
if (binding.activity is FragmentActivity) {
this.binding = binding
this.activity = binding.activity as FragmentActivity
Expand Down Expand Up @@ -468,7 +510,18 @@ public class TruecallerSdkPlugin : FlutterPlugin, MethodCallHandler, EventChanne
}

private fun cleanUp() {
TcSdk.clear()
isCleanedUp.set(true)
// Submit TcSdk.clear() to the executor so it runs after any in-flight
// TcSdk.init() completes — prevents clearing a half-initialized singleton.
// RejectedExecutionException means executor already shut down (no in-flight
// task), so we clear directly on the main thread as a fallback.
try {
ioExecutor.execute { TcSdk.clear() }
} catch (e: java.util.concurrent.RejectedExecutionException) {
TcSdk.clear()
}
// shutdown() lets the queued clear() task finish before the thread terminates.
ioExecutor.shutdown()
launcher = null
binding?.removeActivityResultListener(this)
binding = null
Expand Down