Is it possible to send a synchronous request in the Firebase? - android

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()

Related

Why am I getting ConcurrentModificationException when call Wearable.getMessageClient

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

Does offer make the coroutines close or cancelled?

I was looking at the flow documentation on the Android Developer site and I have a question.
https://developer.android.com/kotlin/flow#callback
If you look at the above link, you will see code like this.
class FirestoreUserEventsDataSource(
private val firestore: FirebaseFirestore
) {
// Method to get user events from the Firestore database
fun getUserEvents(): Flow<UserEvents> = callbackFlow {
// Reference to use in Firestore
var eventsCollection: CollectionReference? = null
try {
eventsCollection = FirebaseFirestore.getInstance()
.collection("collection")
.document("app")
} catch (e: Throwable) {
// If Firebase cannot be initialized, close the stream of data
// flow consumers will stop collecting and the coroutine will resume
close(e)
}
// Registers callback to firestore, which will be called on new events
val subscription = eventsCollection?.addSnapshotListener { snapshot, _ ->
if (snapshot == null) { return#addSnapshotListener }
// Sends events to the flow! Consumers will get the new events
try {
offer(snapshot.getEvents())
} catch (e: Throwable) {
// Event couldn't be sent to the flow
}
}
// The callback inside awaitClose will be executed when the flow is
// either closed or cancelled.
// In this case, remove the callback from Firestore
awaitClose { subscription?.remove() }
}
}
In the code above, awaitClose is explained to be executed when the coroutine is closed or cancelled.
But, there is no close() in the code except for the try-catch statement that initializes the eventsCollection.
Additionally, says offer does not add the element to the channel and **returns false** immediately at the bottom of the Android Developer page.
My question is, in the code above, when offer(snapshot.getEvents()) is executed, does the coroutine cancel with return false, so awaitClose is executed?
Expectation:
As the documentation says:
When you try to add a new element to a full channel, send suspends the
producer until there's space for the new element, whereas offer does
not add the element to the channel and returns false immediately.
Ergo:
It Immediately adds the specified element to this channel, if this doesn’t violate its capacity restrictions, and returns the successful result. Otherwise, returns failed or closed result. This is synchronous variant of send, which backs off in situations when send suspends or throws.
So when trySend call returns a non-successful result, it guarantees that the element was not delivered to the consumer, and it does not call onUndeliveredElement that was installed for this channel. See “Undelivered elements” section in Channel documentation for details on handling undelivered elements.
Conclusion:
A typical usage for onDeliveredElement is to close a resource that is being transferred via the channel. The following code pattern guarantees that opened resources are closed even if producer, consumer, and/or channel are cancelled. Resources are never lost. So no it doesn't return false.

Does RxJava Observable actually observe Retrofit so if I receive data then it will automatically make another exact same call?

So I'm trying to make a messaging part working where a Retrofit API returns a list of messages for a conversation. Sure, the conversation refreshes each time the user chooses to refresh the Activity.
But say the recipient sends me a message, will RxJava Observable automatically make this exact same API call to Retrofit and retrieve the updated data?
If not then what's an easy way I can achieve that?
getApi().getConversationMessagesQuery(request)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((Response<GetConversationDetailsQuery> response) -> {
if (response.body() != null) {
List<Message> messages = response.body().getMessages();
RecyclerView.Adapter adapter = getAdapter();
if (adapter instanceof MessageListAdapter) {
((MessageListAdapter) adapter).setItems(messages, firstLoading);
} else {
adapter = MessageListAdapter.getInstance(messages, firstLoading);
setAdapter(adapter);
}
}
}, (Throwable ex) -> {
}
));
Does the RxJava Observable automatically makes this exact same API call to Retrofit and retrieves the updated data
No, RxJava cannot do that automatically for you.
what's an easy way I can manage to do that?
With simple REST API calls there is no way to detect that the data has changed. You would have to implement some Websocket based technology on your server to achieve this.

Android JWT refresh token flow

I'm implementing codes with jwt on Android.
At point of using refresh token, I'm not sure my code is correct way.
Here is sequene diagram of my flow.
Server issued access token and refresh token. These expire time is 1hour and 3 days. These token is saved to sharedpreferences.
Here is above diagram's description.
When access token is expired, http call will be failed with 401 error.
So I implemented getAccessToken() for re-newing access token.
(1) : One AsyncTask is used for this whole http call step.
- My AsyncTask is too big, I want to refactor it.
(2) : (1)'s AynsTask has a logic for re-getting access token.
- This logic was duplicated all my HTTP call functions.
(3) : After renewing access token, my app re-try to call /api/foo
- To retry it, AsyncTask's doBackground() function is call recursivly.
Here is my code snippet.
class ApplyCheck extends AsyncTask<String, Void, ResponseTypeEnum> {
private List<ApplyEntity> applyEntityList = null;
#Override
protected ResponseTypeEnum doInBackground(String... strings) {
try {
response = restManager.getApplyList(strings[0],"","",""); // call /api/foo
} catch (RestRuntimeException e) {
return ResponseTypeEnum.SERVER_ERROR;
}
switch (response.code()) {
case 200:
//set applyEntityList
....
return ResponseTypeEnum.SUCCESS;
case 401:
//<-- This routine is duplcated all my AsyncTasks
if(getAccessToken()) {
//<-- recursive call to re-call api
return doInBackground(strings);
} else {
return ResponseTypeEnum.TOKEN_EXPIRE;
}
}
}
//re-issue new access token
private boolean getAccessToken() {
Response response = restManager.getAccessToken(); // call /auth/issue-token
if(response.code() == 200) {
String tokens = response.body().string();
JSONObject jsonObject = new JSONObject(tokens);
sharedPreferences.edit().putString("accessToken", jsonObject.getString("accessToken"));
sharedPreferences.edit().putString("refreshToken", jsonObject.getString("refreshToken"));
return true;
} else {
return false;
}
My Questions
1. Is my approach correct? If not, please inform me good practice.
2. If yes, are any good practice for extracting common function for my duplicated AsyncTasks?
The process you have is fine IMHO. The only change is that I would not recursively call doInBackground. What you're doing is feasible, but it violates the intention of doInBackground. Rather modify your AsyncTask to cope with processing different responses in onPostExecute, (ie chaining your requests), and call the AsyncTask again with the relevant parameters for each use case. It will make it much easier to maintain as you can add specific methods to the AsyncTask to cope with each response type and can see how it's triggered in a linear way. If you need to update onProgressUpdate, you should also pass a progress value to the chained AsyncTask calls so it can maintain consistency on the progress. Otherwise it would keep restarting on each call.

How to use RxJava to wait the end of two lists of Retrofit requests

Let me describe my situation:
I want to register new records via an API.
I want to update some records via an API.
I need to be notified when all of these requests have finished, to start another task.
Specifically I have two ArrayList:
ArrayList<Report> createdReports = myHelper.getOfflineCreatedReports();
ArrayList<Report> editedReports = myHelper.getOfflineEditedReports();
Each report can use methods to get Observable instances from my ApiService (Retrofit implementation).
Observable<NewReportResponse> createdReportsObs = Observable.from(createdReports) // .just() != .from()
.flatMap(new Func1<Report, Observable<NewReportResponse>>() {
#Override
public Observable<NewReportResponse> call(Report report) {
return report.postToServer();
}
});
Observable<NewReportResponse> editedReportsObs = Observable.from(editedReports)
.flatMap(new Func1<Report, Observable<NewReportResponse>>() {
#Override
public Observable<NewReportResponse> call(Report report) {
return report.updateInServer();
}
});
I am using the flatMap operator to get one Observable for each report.
But I am not sure how to wait until all of the requests have finished.
I was thinking in using the zip operator.
Observable.zip(createdReportsObs, editedReportsObs, new Func2<NewReportResponse, NewReportResponse, Boolean>() {
#Override
public Boolean call(NewReportResponse justOneResponse, NewReportResponse justOneResponse2) {
return false;
}
});
Unfortunately I saw some examples where zip is used to create pairs of Observables.
Please suggest me what operator I can use to achieve it. Or how to do it using rxJava with a different approach.
Thank you in advance.
Are you using RxJava 2? If so you can use the new completable api. This is assuming you don't need to know any of the server results, just need to wait for them to complete.
Completeable.merge(createdReportsObs.toCompleteable(),
editedReportsObs.toCompleteable())
.subscribe()
This is my way. May not best practice.
Observable.merge(createdReportsObs, editedReportsObs)
.toList()
.flatMap(Observable::from)
.xxx //Now they are completed, do what you want
.subscribe();

Categories

Resources