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());
}
}
It's definitely worth raising as a bug, either for a concurrency fix, or alternatively clearer documentation.
But the MessageClient apis are all async anyway, they return Task that indicate the result when available, so it doesn't seem like you should need to run these on the IO Dispatcher.
You should also be able to use org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.5.2 to have kotlin friendly await methods.
val nodeList = Wearable.getNodeClient(applicationContext).getConnectedNodes().await()
So
Related
When using the Single Responsibility pattern, I wonder what would be the best approach to show network response (Success, error, progress). storing state for every request would create so many states in viewModel and will have to pass so many states to a component. Any other way, worth a try?
I use this in my main Activity's onCreate function to capture all unhandled exceptions but only when the app is released for production. During debug, I want the app to crash so that the exception stack is printed to LogCat:
if (!BuildConfig.DEBUG) {
Thread.setDefaultUncaughtExceptionHandler { paramThread, paramThrowable ->
val message: String
if (paramThrowable is HttpException) {
message = R.string.problem_processing_request.stringRes()
} else {
message = R.string.unknown_problem.stringRes()
}
App.mainViewModel.displaySnackbar(sbInfo = SnackbarInfo(message = message, isCritical = true))
}
}
im currently struggeling with my first more complex rx chain, maybe you can help me out.
I have a list of objects, which i get from an api call. I need to check if they exists local, if they do, update the dataset, if not create them. My current approach is something like this:
private fun insertUpdateFromServer(objectsToInsert: List<Model>): Completable {
return Observable.fromIterable(objectsToInsert).flatMapCompletable { atl ->
dao.getByServerId(atl.id).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.switchIfEmpty { obs: SingleObserver<in Model> -> createNewObject(atl) }.flatMapCompletable {
updateObject(atl, it).applySchedulers()
}
}
Its important, that i have to wait for all the subtasks to complete. This is working if i only have objcts to update, but if there is a new object, the whole thing will not complete.
Note that im only interested if the operation is completed, the emitted object doesnt matter for me. The function "getByServerId" returns a Maybe.
So im asking you if you can point out my logical mistakes and push me in the right direction, thanks in advance!
The problem is with this line:
switchIfEmpty { obs: SingleObserver<in Model> -> createNewObject(atl) }
Kotlin's automatic SAM conversion is trying to make your life easier by generating an overload of switchIfEmpty that implements void subscribe from SingleSource. In not all cases is this helpful however, what you want to be using instead is this:
switchIfEmpty(createNewObject(atl))
A better explanation of why this is happening can be found here.
So i ended up with a workaround which i found here.
TL;DR: Maybe emits null and calls then "onComplete". So i can chain this if i use "onSuccess" and "onComplete".
The code for someone who is interested:
private fun insertUpdateFromServer(objects: List<Model>): Completable {
return Observable.fromIterable(objects)
.flatMapCompletable { obj ->
dao.getByServerId(obj.id).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.doOnComplete { createNewObject(obj).subscribe() }
.doOnSuccess { updateObject(obj, it).applySchedulers().subscribe() }
.flatMapCompletable { Completable.complete() }
}
}
This expression waits for all it subtasks to terminate and terminates then completely.
I'm investigating Kotlin Coroutines & Channels in my current Android application.
I have the following code that manages remote Api calls and controls UI Side effects
private val historical: CompletableDeferred<List<Any>> = CompletableDeferred()
private val mutex = Mutex()
#ExperimentalCoroutinesApi
fun perform(action: Action): ReceiveChannel<List<Any>> =
produce {
mutex.withLock {
if (historical.isCompleted) {
send(historical.getCompleted())
return#produce
}
send(action.sideEffects)
val networkResponse = repository.perform(action)
send(networkResponse.sideEffects)
send(listOf(networkResponse)).also {
historical.complete(listOf(response))
}
}
}
The above code gives me the desired result, however I would like to refactor it to something resembling
the Functional Programming "Railway Pattern" https://android.jlelse.eu/real-world-functional-programming-with-kotlin-arrow-b5a98e72f5e3
where my process flow is
stepOne(Historical.completed)
.stepTwo(action.sideEffects)
.stepThree(getReaction())
.stepFour(reaction.sideEffects)
.finalStep(reaction)
which will "short circuit" on either failures of any step or when Historical "isCompleted"
is it possible to achieve this style of call in Kotlin? and/or Kotlin & Arrow.kt?
You can use Arrow-kt's Either.
You can use Either's mapLeft(), map(), flatMap().
In case result is Exception use mapLeft(). Returned value from mapLeft() going to be new Left in result Either, for example return is String, result going to be Either<String, List<Any>>. In case result is Right, ie List<Any>, mapLeft() going to be skipped, but result type changes anyway, so you will have type of Either<String, List<Any>> with value of Right<List<Any>>. You can also return same Exception from mapLeft() if you choose so
I case you have no need in handling specific errors, you can just chain map() and flatMap(). map() is basically mapRight() and flatMap() is useful when you want to chain calls, ie somewhere in chain there is a receiver of List<Any> which can fail and you want to handle Exception the same way with Either for that call, you can just return new Either from flatMap()
Code gonna look something like this
fun perform(action: Either<Exception, Action>): ReceiveChannel<List<Any>> =
produce {
// if action is always right, you can start it as Right(action) but then first mapLeft does not make any sense
if (historical.completed) action
.mapLeft {
// handle actions exception here
// transform it to something else or just return it
send(action.sideEffects)
it
}.flatMap {
// handle right side of action either
// assume here that repository may fail and returns Either<Exception, NetworkResponse>
repository.perform(it)
}.mapLeft {
// handle repositorys exception here
// transform it to something else or just return it
send(it)
it
}.map {
// handle network response
send(listOf(networkResponse))
historical.complete(listOf(networkResponse))
}
}
I have a network client that is able to resume from interruptions, but needs the last message for doing so when there is a retry.
Example in Kotlin:
fun requestOrResume(last: Message? = null): Flowable<Message> =
Flowable.create({ emitter ->
val connection = if (last != null)
client.start()
else
client.resumeFrom(last.id)
while (!emitter.isDisposed) {
val msg = connection.nextMessage()
emitter.onNext(msg)
}
}, BackpressureStrategy.MISSING)
requestOrResume()
.retryWhen { it.flatMap { Flowable.timer(5, SECONDS) } }
// how to pass the resume data when there is a retry?
Question: as you can see, I need the last received message in order to prepare the resume call. How can I keep track of it so that when there is a retry it is available to make the resume request?
One possible solution may be to create a holder class that just holds a reference to the last message and is updated when a new message is received. This way when there is a retry the last message can be obtained from the holder. Example:
class MsgHolder(var last: Message? = null)
fun request(): Flowable<Message> {
val holder = MsgHolder()
return Flowable.create({ emitter ->
val connection = if (holder.last != null)
client.start()
else
client.resumeFrom(holder.last.id)
while (!emitter.isDisposed) {
val msg = connection.nextMessage()
holder.last = msg // <-- update holder reference
emitter.onNext(msg)
}
}, BackpressureStrategy.MISSING)
}
I think this might work, but it feels like a hack (thread synchronization issues?).
Is there a better way to keep track of the state so it is available for retries?
Note that, unless you rethrow a wrapper around your last element (not too functionally different from your existing "hack"-ish solution but way uglier imo), no error handling operators can recover the last element without some outside help because they only get access to streams of Throwable. Instead, see if the following recursive approach suits your needs:
fun retryWithLast(seed: Flowable<Message>): Flowable<Message> {
val last$ = seed.last().cache();
return seed.onErrorResumeNext {
it.flatMap {
retryWithLast(last$.flatMap {
requestOrResume(it)
})
}
};
}
retryWithLast(requestOrResume());
The biggest distinction is caching the trailing value from the last attempt in an Observable with cache rather than doing so manually in a value. Note also that the recursion in the error handler means retryWithLast will continue to extend the stream if subsequent attempts continue failing.
Take a close look to buffer() operator: link
You could use it like this:
requestOrResume()
.buffer(2)
From now, your Flowable will return List<Message> with two latests objects
I'm using Firebase Android SDK and became interested in sending synchronous request instead of asynchronous. According to the documentation, in any request callbacks are presented. But what about the synchronicity?
Thanks!
There is no way to synchronously load data from the Firebase Database.
While it is common for developers new to Firebase to wish for a synchronous method, it simply doesn't fit with Firebase's data synchronization model. Also see my answer here: Setting Singleton property value in Firebase Listener
It is not possible to load data synchronously with the official SDK. However, you can access all the data in firebase using the REST API. This would allow you to make synchronous calls. As mentioned about, Firebase is a realtime database and you will be missing the feature of updates when your data changes.
I made a simple class to call tasks synchronously in Android.
Note that this is similar to Javascript's async await function.
Check my gist.
TasksManager.class
public class TasksManager {
...
public ExecutorService getExecutor() {
if (mDefaultExecutor == null || mDefaultExecutor.isShutdown() || mDefaultExecutor.isTerminated()) {
// Create a new ThreadPoolExecutor with 2 threads for each processor on the
// device and a 60 second keep-alive time.
int numCores = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
numCores * 2,
numCores * 2,
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>()
);
mDefaultExecutor = executor;
}
return mDefaultExecutor;
}
public static <TResult> Task<TResult> call(#NonNull Callable<TResult> callable) {
return Tasks.call(getInstance().getExecutor(), callable);
}
}
Here's a sample code to use it.
TasksManager.call(() -> {
Tasks.await(AuthManager.signInAnonymously());
// You can use multiple Tasks.await method here.
// Tasks.await(getUserTask());
// Tasks.await(getProfileTask());
// Tasks.await(moreAwesomeTask());
// ...
startMainActivity();
return null;
}).addOnFailureListener(e -> {
Log.w(TAG, "signInAnonymously:ERROR", e);
});
While it is not possible to load data from the FirebaseDatabase in a synchronous way, it is possible to wait for the load to finish synchronously.
You can wrap your value listener in a CountDownLatch and count down,
once the onDataChange or onCancelled implementation is called.
This is actually what the Tasks api is doing internally if you call Tasks.await(someTask).
You should use the value listener for single event listening, because in this case I assume you don't want continued updates. And use a proper timeout for the CountDownLatch, since Firebase won't timeout, ever.
reference.addListenerForSingleValueEvent(...);
You also have to take into account, that if you have the FirebaseDatabase
cache enabled, the first result might not be the actual value on the
server.
I have to add: While this might work, it is against the idea how firebase is designed and supposed to be used, as Frank already said.
If you are using Kotlin, add an extension function:
private suspend fun <TResult> Task<TResult>.await(): TResult? {
return try {
Tasks.await(this)
} catch (e: Exception) {
null
}
}
Now you can do
val snapshot = fireStore.collection(USER_ROOT_PATH).document(documentPath)?.get()?.await()