How to use kotlin coroutines in firebase database - firebase

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

Related

What Does WidgetsFlutterBinding.ensureInitialized() do?

I am trying to use the Firebase package with the below line of code.
I really want to know what this line of code actually does?
The official documentation didn't help me much. Can someone explain me, please?
You have to use it, in this way:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
https://flutter.dev/docs/resources/architectural-overview#architectural-layers
The above image is the architecture layers of Flutter, the WidgetFlutterBinding is used to interact with the Flutter engine. Firebase.initializeApp() needs to call native code to initialize Firebase, and since the plugin needs to use platform channels to call the native code, which is done asynchronously therefore you have to call ensureInitialized() to make sure that you have an instance of the WidgetsBinding.
From the docs:
Returns an instance of the WidgetsBinding, creating and initializing it if necessary. If one is created, it will be a WidgetsFlutterBinding. If one was previously initialized, then it will at least implement WidgetsBinding.
You only need to call this method if you need the binding to be initialized before calling runApp.
From the source code:
#override
Future<FirebaseAppPlatform> initializeApp(
{String name, FirebaseOptions options}) async {
if (name == defaultFirebaseAppName) {
throw noDefaultAppInitialization();
}
// Ensure that core has been initialized on the first usage of
// initializeApp
if (!isCoreInitialized) {
await _initializeCore();
}
// If no name is provided, attempt to get the default Firebase app instance.
// If no instance is available, the user has not set up Firebase correctly for
// their platform.
if (name == null) {
MethodChannelFirebaseApp defaultApp =
appInstances[defaultFirebaseAppName];
if (defaultApp == null) {
throw coreNotInitialized();
}
return appInstances[defaultFirebaseAppName];
}
assert(options != null,
"FirebaseOptions cannot be null when creating a secondary Firebase app.");
// Check whether the app has already been initialized
if (appInstances.containsKey(name)) {
throw duplicateApp(name);
}
_initializeFirebaseAppFromMap(await channel.invokeMapMethod(
'Firebase#initializeApp',
<String, dynamic>{'appName': name, 'options': options.asMap},
));
return appInstances[name];
}
The invokeMapMethod will invoke a method on the above channel with the specified arguments, which will then call the initializeApp() method in the native code,
https://github.com/FirebaseExtended/flutterfire/blob/master/packages/firebase_core/firebase_core/android/src/main/java/io/flutter/plugins/firebase/core/FlutterFirebaseCorePlugin.java#L227
There are also different ways to initialize Firebase, which you can check here:
No Firebase App '[DEFAULT]' has been created - call Firebase.initializeApp() in Flutter and Firebase
In the other ways we do not call WidgetsFlutterBinding.ensureInitialized() since the runApp() function calls it internally:
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..scheduleAttachRootWidget(app)
..scheduleWarmUpFrame();
}
https://github.com/flutter/flutter/blob/bbfbf1770c/packages/flutter/lib/src/widgets/binding.dart#L1012
A simple answer is that if Flutter needs to call native code before calling runApp
WidgetsFlutterBinding.ensureInitialized();
makes sure that you have an instance of the WidgetsBinding, which is required to use platform channels to call the native code.
You only need to call this method if you need the binding to be
initialized before calling runApp.
A simple answer, you need to use this line, if your main function uses async keyword because you use await statement inside it.
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences prefs = await SharedPreferences.getInstance(); // just an example
}

Go back to main-thread inside coroutine?

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

Async retrieving data from Firebase Firestore Kotlin [duplicate]

Firebase anonymous sign in returns a task (which is basically Google promise implementation):
val task:Task<AuthResult> = FirebaseAuth.getInstance().signInAnonymously()
How it would be possible create a signInAnonymous wrapper where:
It is a suspend function, waiting for the task completion
suspend fun signInAnonymous(): Unit
It returns a Deferred object, delivering the result asynchronously
fun signInAnonymous() : Deferred
The package kotlinx.coroutines.tasks now includes the follwing utility functions:
public suspend fun <T> Task<T>.await(): T { ... }
From the docs:
Awaits for completion of the task without blocking a thread.
This suspending function is cancellable.
If the Job of the current coroutine is cancelled or completed while this suspending function is waiting, this function stops waiting for the completion stage and immediately resumes with CancellationException.
public fun <T> Task<T>.asDeferred(): Deferred<T> { ... }
From the docs:
Converts this task to an instance of Deferred.
If task is cancelled then resulting deferred will be cancelled as well.
So you can just do:
suspend fun signInAnonymouslyAwait(): AuthResult {
return FirebaseAuth.getInstance().signInAnonymously().await()
}
or:
fun signInAnonymouslyDeferred(): Deferred<AuthResult> {
return FirebaseAuth.getInstance().signInAnonymously().asDeferred()
}
Based on this GitHub library, here's a way to transform a Task into a suspending function in the "usual" way to adapt callback based async calls to coroutines:
suspend fun <T> Task<T>.await(): T = suspendCoroutine { continuation ->
addOnCompleteListener { task ->
if (task.isSuccessful) {
continuation.resume(task.result)
} else {
continuation.resumeWithException(task.exception ?: RuntimeException("Unknown task exception"))
}
}
}
You can also wrap it in a Deferred of course, CompletableDeferred comes in handy here:
fun <T> Task<T>.asDeferred(): Deferred<T> {
val deferred = CompletableDeferred<T>()
deferred.invokeOnCompletion {
if (deferred.isCancelled) {
// optional, handle coroutine cancellation however you'd like here
}
}
this.addOnSuccessListener { result -> deferred.complete(result) }
this.addOnFailureListener { exception -> deferred.completeExceptionally(exception) }
return deferred
}
Add this to gradle
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.4.3'
And then you can use it like this:
suspend fun login(email: String, pass: String) {
FirebaseAuth.getInstance().signInWithEmailAndPassword(email, pass).await()
}
To transform it into a coroutine-ready function, I would use the Tasks.await() function from the Tasks API:
suspend fun FirebaseAuth.signInAnonymouslyAwait(): AuthResult {
return Tasks.await(this.signInAnonymously())
}
As for Deferred, i'd stick with zsmb13's answer

Kotlin continuation doesn't resume

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.

Hacklang async code example?

How modify the following code to get article data and top articles asynchronously in hack ?
class ArticleController
{
public function viewAction()
{
// how get
$article = $this->getArticleData();
$topArticles = $this->getTopArticles();
}
private function getArticleData() : array
{
// return article data from db
}
private function getTopArticles() : array
{
// return top articles from db
}
}
The warning from the async documentation page is relevant here:
There is currently basic support for async. For example, you can
currently write basic async functions that call other async functions.
However, we are currently finalizing other foundations (e.g. async
database, scheduling, and memory handling APIs) which will be required
to reach the full potential of async in production. We felt, though,
it would be useful to introduce the concept and technology of async
(even with basic functionality) in order to get developers used to the
syntax and some of the technical details.
So, the raw database queries you need to actually make use of async functions are unfortunately not available yet. The documentation linked above talks some about how async functions work in general, and includes an example of coalesced fetching, something that you can do with async functions right now.
The DB API is coming eventually, but isn't available yet, sorry!
HHVM 3.6 and newer
async functions info
The two HHVM PHP language keywords that enable async functions are async and await. async declares a function as asynchronous. await suspends the execution of an async function until the result of the asynchronous operation represented by await is available. The return value of a function that await can be used upon is an object that implements Awaitable<T>.
You have an example in the documentation (1). There is a discussion about asynchronous functions in the language specification as well (2).
It actually took me some time to realize how to use and call the asynchronous functions, so I think you will find some more info useful.
We have these two functions: foo() and bar().
async function foo(): Awaitable<void> {
print "executed from foo";
}
async function bar(int $n): Awaitable<int> {
print "executed from bar";
return $n+1;
}
Let's experiment some ways to call these two functions:
foo(); // will print "executed from foo"
bar(15); // will print "executed from bar"
$no1 = bar(15); // will print "executed from bar"
print $no1; // will output error, because $number is not currently an `int`; it is a `WaitHandle`
$no2 = bar(15)->join(); // will print "executed from bar"
print $no2; // will print 16
AsyncMysqlClient tips
The connection to a MySQL database is made with AsyncMysqlClient::connect asynchronous function which returns an ExternalThreadEventWaitHandle to an AsyncMysqlConnection.
You can perform query or queryf on an AsyncMysqlConnection. Note: the data you send to a queryf is properly escaped by the function.
A query you perform on an AsyncMysqlConnection returns either an AsyncMysqlQueryResult (when the query performs ok) or AsyncMysqlQueryErrorResult (if the query goes wrong; then you can treat errors with the mysql_error(), mysql_errno() and failureType() members of this class). Both AsyncMysqlQueryResult and AsyncMysqlQueryErrorResult extend AsyncMysqlResult abstract class.
Below is a probable implementation of your class:
class ArticleController {
private AsyncMysqlConnection $connection;
public async function viewAction(int $articleId): Awaitable<void> {
$this->connection = await AsyncMysqlClient::connect( /* connection data */ );
$article = await $this->getArticleData($articleId);
}
public async function getArticleData(int $id): Awaitable<?Vector> {
$articleDataQuery = await $this->connection->queryf("SELECT * FROM articles WHERE id %=d", $id);
if($articleDataQuery instanceof AsyncMysqlQueryErrorResult) {
throw new Exception("Error on getting data: ".$articleDataQuery->mysql_error());
}
// Considering that $id represents a unique id in your database, then
// you are going to get only one row from your database query
// so you return the first (and only) row in the query result
if($articleDataQuery->numRows() == 1) {
return $articleDataQuery->mapRowsTyped()[0];
}
return null;
}
}
P.S. I hope it is not too late for this answer and I hope it helps you. If you consider this useful, please, accept it.

Resources