Im running this code since addListenerForSingleEvent is a long Running operation:
CoroutineScope(IO).launch {
userRef.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {
}
override fun onDataChange(p0: DataSnapshot) {
if (p0.exists()) {
withContext(Main) {
toggleLoading()
val intent = Intent(this#LogInActivity, MainActivity::class.java)
startActivity(intent)
finish()
}
} else{
withContext(Main) {
var addUsernameIntent = Intent(this#LogInActivity,
AddUsernameActivity::class.java)
startActivityForResult(addUsernameIntent, CHOOSE_USERNAME_REQUEST)
}
}
}
})
}
I get an error where i write withContext(Main) that says :
Suspension functions can be called only within coroutine body
But I have a coroutine body right? Before i just had a Thread(runnable {..}) instead of a couroutine, but i read that i shouldn't do intents inside any other Thread than main-thread so i changed to coroutine.
The Firebase client already runs any network and disk I/O operations on a separate thread. There almost never is a need to run addListenerForSingleEvent on a separate thread yourself.
Also see:
Do we need to use background thread for retrieving data using firebase?
Firebase Android: onDataChange() event always executed in Main UI Thread?
Firebase database - run on different thread
Call method in another thread on completion
Function of an anonymous object may capture the variables of the scope, but it is not in enclosing coroutine body. Replace withContext(Main) with creating a new coroutine: <CoroutineScope>.launch(Main).
Related
fun viewDidLoad() {
observeViewModel()
initializeUI()
getVideos()
getQuestions()
}
fun initializeUI() {
/*
1 - Create a list as;
VideosTitleRow("Videos"),
VideosBodyRow(null),
QuestionTitleRow("Questions"),
QuestionsBodyRow(null),
...."
Note: The bodies are null right now. There will fill with correct values after.
---
2 - Call updateUI functions for update ui with initialize list.
*/
}
fun getVideos() {
viewModel.getVideos()
}
fun getQuestions() {
viewModel.getQuestions()
}
fun observeViewModel() {
// Videos
CoroutineScope(Dispatchers.IO).launch {
when (flow) {
// ...
is VideosFlow.DataReceived -> {
val row = SectionVideosBodyData(flow.videos)
updateUI(1, row)
}
}
}
// Questions
CoroutineScope(Dispatchers.IO).launch {
when (flow) {
// ...
is QuestionsFlow.DataReceived -> {
val row = SectionQuestionsBodyData(flow.questions)
updateUI(3, row)
}
}
}
// And others LiveDatas under observing ....
}
fun updateUI(indexAt: Int? = null, row: BaseAdapterData = null) {
/*
If indexAt and row parameters are not null;
edit the global row list then created new row list from this (global list) and send the new list to adapter for update ui.
If parameters are null; send the global list to adapter for update ui.
*/
}
This is the current structure.
Let me try to explain what is problem;
When i start the initializeUI() function i know when it will finish the job. Because this is a synchronous function.
getVideos() or getQuestions() functions are a synchronous functions too but they start to api call, send request, take response, convert to ui model, bla bla operations. And we have to observe the results. So, we don't know when this job will finish!
getVideos() function just like says to viewModel.getVideos; "Let me know when you see the red car" but we can't know when is the red car pass on street.
In this case; although getQuestions() function is called before getVideos(), it may finish before it. In fact, both can end at the same time.
Here is i need;
I want to use kotlin coroutines scopes in getVideos(), getQuestions(), ... functions run as asynchronous but every job must wait for finished updateUI() function.
1- initializeUI() function runs, create a list, send to adapter, update the ui,
2- getVideos() or getQuestions() runs as asynchronous,
let say; livedata(getVideos) finish the their work but livedata (getQuestions) still working.
getQuestion() when call the updateUI() function, getVideos() can not call until updateUI() function finished called from getQuestion()
I know this is a complicated case :)
But the LiveData is messing up my mind.
I tried Mutex but although getVideos() or getQuestions() functions are synchronous, they starts an asynchronous job.
(Kotlin Coroutines sequential execution)
I am trying to solve an issue whereby I need to combine third party events with the eventBus send and reply approach that Vertx provides for Standard and Worker Verticle setups. I am not sure if what I have laid out below is necessarily the correct approach.
Problem Statement:
I want to have standard verticle that sends a message to a worker verticle.
The worker verticle does some preprocessing and then uses a client method provided by a third party state management lib to publish an even (in an async manner). The result of which is only whether or not the event was successfully received or not (but does not contain any further info around processing etc).
Further processing takes place when the third party state management lib receives the event(this all happens on a separate thread) and a success or failure can occur at which point another event will be published to the cluster management tools output channel.
From the output channel listener I then want to be able to use the event to somehow use the message.reply() on the worker verticle to send back a response to the standard verticle that made the original request, thereby closing the loop of the entire request lifecycle but also using the async approach that vertx is built to use.
Now I conceptually know how to do 90% of what is described here but the missing piece for me is how to coordinate the event on the output channel listener and connect this to the worker verticle so that I can trigger the message.reply.
I have looked at possibly using SharedData and Clustering that Vertx has but was wondering if there is possibly another approach.
I have put a possible example implementation but would really appreciate if anyone has any insights/thoughts into how this can be accomplished and if I am on the right track.
class Order(val id: String)
class OrderCommand(val order: Order) : Serializable {
companion object {
const val name = "CreateOrderCommand"
}
}
class SuccessOrderEvent(val id: String) : Serializable {
companion object {
const val name = "OrderSuccessfulEvent"
}
}
interface StateManagementLib {
fun <T> send(
value: T,
success: Handler<AsyncResult<Int>>,
failure: Handler<AsyncResult<Exception>>
) {
val output = publish(value)
if (output == 1) {
success.handle(Future.succeededFuture())
} else {
failure.handle(Future.failedFuture("Failed"))
}
}
// non-blocking
fun <T> publish(value: T): Int // returns success/failure only
}
class WorkVerticle constructor(private val lib: StateManagementLib) : AbstractVerticle() {
override fun start(startPromise: Promise<Void>) {
workerHandler()
startPromise.complete()
}
private fun workerHandler() {
val consumer = vertx.eventBus().consumer<OrderCommand>(OrderCommand.name)
consumer.handler { message: Message<OrderCommand> ->
try {
vertx.sharedData().getClusterWideMap<String, Message<OrderCommand>>("OrderRequest") { mapIt ->
if (mapIt.succeeded()) {
lib.send(message.body(), {
// The StateManagementLib successfully propagated the event so, we try and store in this map (id -> Message)
mapIt.result().put(message.body().order.id, message)
}, {
message.fail(400, it.result().message)
})
}
}
} catch (e: Exception) {
message.fail(
HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), "Failed to encode data."
)
}
}
// A consumer that will pick up an event that is received from the clusterTool output channel
vertx.eventBus().addInboundInterceptor { context: DeliveryContext<SuccessOrderEvent> ->
// Retrieve cluster map to get the previously stored message and try to respond with Message.reply
// This should go back to the Standard Verticle that sent the message
vertx.sharedData().getClusterWideMap<String, Message<OrderCommand>>("OrderRequest") {
if (it.succeeded()) {
val id = context.message().body().id
val mapResult = it.result().get(id)
it.result().remove(id)
// Try and reply so the original eventloop thread can pickup and respond to calling client
mapResult.result().reply(id)
}
}
}
}
}
I'm trying to get my head around suspendCoroutine and suspendCancellableCoroutine. I think they could be useful in the following case:
When the coroutine is launched, check if the user is logged in.
If not, ask for credentials and pause the currently executing coroutine.
When the credentials are submitted, resume the coroutine from the same line where it was suspended.
This compiles but never makes it past "delay over", i.e. the continuation never resumes:
import kotlinx.coroutines.*
fun main(args: Array<String>) {
println("Hello, world!")
runBlocking {
launch {
postComment()
}
}
}
var isLoggedIn = false
var loginContinuation: CancellableContinuation<Unit>? = null
suspend fun postComment() {
if (!isLoggedIn) {
showLoginForm()
suspendCancellableCoroutine<Unit> {
loginContinuation = it
}
}
// call the api or whatever
delay(1000)
println("comment posted!")
}
suspend fun showLoginForm() {
println("show login form")
// simulate delay while user enters credentials
delay(1000)
println("delay over")
isLoggedIn = true
// resume coroutine on submit
loginContinuation?.resume(Unit) { println("login cancelled") }
}
I've tried everything I can think of, including moving the call to suspendCancellableCoroutine outside of the login check, wrapping the contents of showLoginForm in withContext(Dispatchers.IO), using coroutineScope.launch(newSingleThreadContext("MyOwnThread"), etc. The impression I get from reading the internet is that this is a valid use case. What am I doing wrong?
First of all, you misunderstand the concept of suspend functions. Calling function showLoginForm() does not start a new coroutine. Code in a single coroutine is always executed sequentially - at first you call showLoginForm(), it delays, it does not resume any continuations because loginContinuation is null, and then suspendCancellableCoroutine suspends your coroutine forever and causes a deadlock.
Starting a new coroutine that executes showLoginForm() can make your code work:
suspend fun CoroutineScope.postComment() {
if (!isLoggedIn) {
launch {
showLoginForm()
}
suspendCancellableCoroutine<Unit> {
loginContinuation = it
}
}
// call the api or whatever
delay(1000)
println("comment posted!")
}
This code still can fail (*), but in this particular case it does not. Working version of this code can look like this:
import kotlin.coroutines.*
import kotlinx.coroutines.*
fun main(args: Array<String>) {
println("Hello, world!")
runBlocking {
postComment()
}
}
var isLoggedIn = false
suspend fun CoroutineScope.postComment() {
if (!isLoggedIn) {
suspendCancellableCoroutine<Unit> { continuation ->
launch {
showLoginForm(continuation)
}
}
}
delay(1000)
println("comment posted!")
}
suspend fun showLoginForm(continuation: CancellableContinuation<Unit>) {
println("show login form")
delay(1000)
println("delay over")
isLoggedIn = true
continuation.resume(Unit) { println("login cancelled") }
}
Also, in your example suspending coroutines is not needed. Why do we need another coroutine if we can just execute its code in the same coroutine? We need to wait until it finishes anyway. Since coroutines execute code sequentially, we will go to the code after if branch only after showLoginForm() finishes:
var isLoggedIn = false
suspend fun postComment() {
if (!isLoggedIn) {
showLoginForm()
}
delay(1000)
println("comment posted!")
}
suspend fun showLoginForm() {
println("show login form")
delay(1000)
println("delay over")
isLoggedIn = true
}
This approach is the best for your example, where all code is sequential.
(*) - This code still can cause deadlock if suspendCancellableCoroutine is invoked after showLoginForm finishes - for example, if you remove delay call in showLoginForm or if you use a multithreaded dispatcher - in JVM there is no guarantee that suspendCancellableCoroutine will be invoked earlier than showLoginForm. Moreover, loginContinuation is not #Volatile, so with multithreaded dispatcher the code can fail also from visibility issues - thread that executes showLoginForm may observe that loginContinuation is null.
Passing around Continuations is messy and can easily lead to the error you have...one function finishes before the continuation has even been assigned to the continuation property.
Since the login form is what you want to turn into a suspend function, that's where you should use suspendCoroutine. suspendCoroutine is low level code that you should put as low as possible so your main program logic can use easy-to-read sequential coroutines without the nested launch/suspendCoroutine calls.
var isLoggedIn = false
suspend fun postComment() {
if (!isLoggedIn) {
showLoginForm()
}
println("is logged in: $isLoggedIn")
if (isLoggedIn) {
// call the api or whatever
delay(1000)
println("comment posted!")
}
}
suspend fun showLoginForm(): Unit = suspendCancellableCoroutine { cont ->
println("Login or leave blank to cancel:")
//Simulate user login or cancel with console input
val userInput = readLine()
isLoggedIn = !userInput.isNullOrBlank()
cont.resume(Unit)
}
I didn't use delay() in showLoginForm() because you can't call suspend functions within a suspendCancellableCoroutine block. Those last three lines could also be wrapped in a scope.launch and use delay instead of readLine, but in reality, your UI interaction wouldn't be a coroutine with a delay anyway.
EDIT:
Trying to pass a continuation to another Activity would be especially messy. Google does not even recommend using multiple Activities in an app because it is difficult to pass objects between them. To do it with Fragments, you could maybe write your LoginFragment class to have a private continuation property like this:
class LoginFragment(): Fragment {
private val continuation: Continuation<Boolean>? = null
private var loginComplete = false
suspend fun show(manager: FragmentManager, #IdRes containerViewId: Int, tag: String? = null): Boolean = suspendCancelableCoroutine { cont ->
continuation = cont
retainInstance = true
manager.beginTransaction().apply {
replace(containerViewId, this#LoginFragment, tag)
addToBackStack(null)
commit()
}
}
// Call this when login is complete:
private fun onLoginSuccessful() {
loginComplete = true
activity?.fragmentManager?.popBackStack()
}
override fun onDestroy() {
super.onDestroy()
continuation?.resume(loginComplete)
}
}
Then you would show this fragment from another fragment like this:
lifecycleScope.launch {
val loggedIn = LoginFragment().show(requireActivity().fragmentManager, R.id.fragContainer)
// respond to login state here
}
So long as you are using a Fragment's lifecycleScope rather than an Activity's lifecycleScope and the first Fragment also uses retainInstance = true, I think you should be safe from screen rotations. But I haven't done this myself.
I'm trying to access chatting room Using firestore and coroutines.
fun getOwner() {
runBlocking {
var de = async(Dispatchers.IO) {
firestore.collection("Chat").document("cF7DrENgQ4noWjr3SxKX").get()
}
var result = de.await().result
}
But i get error like this :
E/AndroidRuntime: FATAL EXCEPTION: Timer-0
Process: com.example.map_fetchuser_trest, PID: 19329
java.lang.IllegalStateException: Task is not yet complete
at com.google.android.gms.common.internal.Preconditions.checkState(Unknown Source:29)
at com.google.android.gms.tasks.zzu.zzb(Unknown Source:121)
at com.google.android.gms.tasks.zzu.getResult(Unknown Source:12)
at com.example.map_fetchuser_trest.model.Repository$getOwner$1.invokeSuspend(Repository.kt:53)
How can i get chat document? when i use origin api like below, I can access chat room document.
firestore.collection("Chat").document(
"cF7DrENgQ4noWjr3SxKX"
).get().addOnCompleteListener { task ->
if (task.isSuccessful) {
val chatDTO = task.result?.toObject(Appointment::class.java)
}
}
Task is the thing one awaits on, but you wrapped it in another layer of async. Remove the async:
fun getOwner() {
runBlocking {
var de = firestore.collection("Chat").document("cF7DrENgQ4noWjr3SxKX").get()
var result = de.await().result
}
}
However, by using runBlocking() you have shot yourself in the foot and wrote blocking code that just formally uses an async API to no good effect.
To truly benefit from it, you must have a
suspend fun getOwner() = firestore
.collection("Chat")
.document("cF7DrENgQ4noWjr3SxKX")
.get()
.await()
.result
and launch a coroutine at the place you call it from:
launch {
val owner = getOwner()
// update the GUI
}
This assumes you're calling launch from an object that is a CoroutineScope.
val db = FirebaseFirestore.getInstance()
override suspend fun saveBinToDB(bin: Bin): Result<Unit> {
lateinit var result:Result<Unit>
db.collection("bins")
.add(bin)
.addOnSuccessListener { documentReference ->
Log.d(TAG, "DocumentSnapshot written with ID: ${documentReference.id}")
result = Result.Success(Unit)
}
.addOnFailureListener { e ->
Log.w(TAG, "Error adding document", e)
result = Result.Error(Exception())
}
.await()
return result
}
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.3.7"
The usage of runBlocking{..} in the first code snippet works as follows: the runBlocking function blocks to execute the parameter lambda code (and the lambda code will suspend inside). It gives less sense.
You may want to start a coroutine with launch{..} function instead and use withContext(Dispatchers.Main){..} to have the block executed in the UI thread, e.g. to show the fetched results. You may also implement CoroutineScope in your activity class.
The first step - you need to turn the Firebase API call into a suspending function. It could be done with suspendCoroutine{..} function (there are several more functions like suspendCancellableCoroutine{..} in the kotlinx.coroutines library.
There is an integration library with Google Play Services that provides support for Firebase
https://github.com/Kotlin/kotlinx.coroutines/tree/master/integration/kotlinx-coroutines-play-services
I struggled to get await() function. Check your build.gradle should have below dependencies -
def coroutines = '1.6.4'
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
api "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$coroutines"
Now instead of Complete/Failure Listener, use await() just like used in #Marko answer -
suspend fun getDataFromFireStore() = firestore
.collection("some key")
.document("some key")
.get()
.await()
.documents
I m sending several retrofit calls via SyncAdapter onPerformSync and I m trying to regulate http calls by sending out via a try/catch sleep statement. However, this is blocking the UI and will be not responsive only after all calls are done.
What is a better way to regulate network calls (with a sleep timer) in background in onPerformSync without blocking UI?
#Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
String baseUrl = BuildConfig.API_BASE_URL;
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
service = retrofit.create(HTTPService.class);
Call<RetroFitModel> RetroFitModelCall = service.getRetroFit(apiKey, sortOrder);
RetroFitModelCall.enqueue(new Callback<RetroFitModel>() {
#Override
public void onResponse(Response<RetroFitModel> response) {
if (!response.isSuccess()) {
} else {
List<RetroFitResult> retrofitResultList = response.body().getResults();
Utility.storeList(getContext(), retrofitResultList);
for (final RetroFitResult result : retrofitResultList) {
RetroFitReview(result.getId(), service);
try {
// Sleep for SLEEP_TIME before running RetroFitReports & RetroFitTime
Thread.sleep(SLEEP_TIME);
} catch (InterruptedException e) {
}
RetroFitReports(result.getId(), service);
RetroFitTime(result.getId(), service);
}
}
}
#Override
public void onFailure(Throwable t) {
Log.e(LOG_TAG, "Error: " + t.getMessage());
}
});
}
}
The "onPerformSync" code is executed within the "SyncAdapterThread" thread, not within the Main UI thread. However this could change when making asynchronous calls with callbacks (which is our case here).
Here you are using an asynchronous call of the Retrofit "call.enqueue" method, and this has an impact on thread execution. The question we need to ask at this point:
Where callback methods are going to be executed?
To get the answer to this question, we have to determine which Looper is going to be used by the Handler that will post callbacks.
In case we are playing with handlers ourselves, we can define the looper, the handler and how to process messages/runnables between handlers. But this time it is different because we are using a third party framework (Retrofit). So we have to know which looper used by Retrofit?
Please note that if Retrofit didn't already define his looper, you
could have caught an exception saying that you need a looper to
process callbacks. In other words, an asynchronous call needs to be in
a looper thread in order to post callbacks back to the thread from
where it was executed.
According to the code source of Retrofit (Platform.java):
static class Android extends Platform {
#Override CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {
if (callbackExecutor == null) {
callbackExecutor = new MainThreadExecutor();
}
return new ExecutorCallAdapterFactory(callbackExecutor);
}
static class MainThreadExecutor implements Executor {
private final Handler handler = new Handler(Looper.getMainLooper());
#Override public void execute(Runnable r) {
handler.post(r);
}
}
}
You can notice "Looper.getMainLooper()", which means that Retrofit will post messages/runnables into the main thread message queue (you can do research on this for further detailed explanation). Thus the posted message/runnable will be handled by the main thread.
So that being said, the onResponse/onFailure callbacks will be executed in the main thread. And it's going to block the UI, if you are doing too much work (Thread.sleep(SLEEP_TIME);). You can check it by yourself: just make a breakpoint in "onResponse" callback and check in which thread it is running.
So how to handle this situation? (the answer to your question about Retrofit use)
Since we are already in a background thread (SyncAdapterThread), so there is no need to make asynchronous calls in your case. Just make a Retrofit synchronous call and then process the result, or log a failure. This way, you will not block the UI.