This looks like a concurrency bug inside Google Wearable API, but let me know if I'm wrong and suggest a way around. The purpose of this code is to get a Wearable message client and send a message to a WearOS device.
I need to do it asynchronously to avoid locking the main thread. It looks like Google's implementation of getMessageClient is not thread safe. Should I lock on some object to avoid the crashes? What would be the best approach here? Why Google couldn't add a callback in a thread safe manner?
IMO, all they needed to do was to lock the SimpleArrayMap object before "put" is called and release after "put" is completed.
fun sendToWear(msg:String) {
lifecycle.coroutineScope.launch {
withContext(Dispatchers.IO) {
val nodeListTask = Wearable.getNodeClient(applicationContext).getConnectedNodes()
try {
val nodes = Tasks.await<List<Node>>(nodeListTask)
Tasks.await<List<Node>>(nodeListTask, 10, TimeUnit.SECONDS)
for (node in nodes) {
//Send the message///
val sendMessageTask = Wearable.getMessageClient(this@RootActivity).sendMessage(node.getId(),
BROADCAST_PATH, msg.toByteArray())
Stack trace:
java.util.ConcurrentModificationException:
at androidx.collection.SimpleArrayMap.put (SimpleArrayMap.java:482)
at com.google.android.gms.common.api.internal.zzc.addCallback (zzc.java:20)
at com.google.android.gms.common.api.internal.zaae.<init> (zaae.java:14)
at com.google.android.gms.common.api.internal.zaae.zaa (zaae.java:5)
at com.google.android.gms.common.api.GoogleApi.<init> (GoogleApi.java:31)
at com.google.android.gms.wearable.MessageClient.<init> (MessageClient.java:3)
at com.google.android.gms.wearable.internal.zzez.<init> (zzez.java:4)
at com.google.android.gms.wearable.Wearable.getMessageClient (Wearable.java:11)
at info.gryb.gac.mobile.fragments.RootActivity$sendToWear$1$1.invokeSuspend (RootActivity.kt:1048)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run (Dispatched.kt:241)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely (CoroutineScheduler.kt:594)
at kotlinx.coroutines.scheduling.CoroutineScheduler.access$createdWorkers (CoroutineScheduler.kt:60)
at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely (CoroutineScheduler.kt:60)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run (CoroutineScheduler.kt:740)
It's crashing while trying to add a callback by calling "put" in Google API's code below:
public final void addCallback(String var1, @NonNull LifecycleCallback var2) {
if (!this.zzbe.containsKey(var1)) {
this.zzbe.put(var1, var2); <-- Crashes in this put
if (this.zzbf > 0) {
(new zze(Looper.getMainLooper())).post(new zzd(this, var2, var1));
}
} else {
throw new IllegalArgumentException((new StringBuilder(59 + String.valueOf(var1).length())).append("LifecycleCallback with tag ").append(var1).append(" already added to this fragment.").toString());
}
}