Firebase And Kotlin Coroutines - firebase

I have this static (companion object) function to download event information from Firebase Firestore and event images from Firebase Storage:
fun downloadEventInformationAndImages() {
FirebaseFirestore.getInstance().collection("events").document(downloadedEventID)
.get().addOnSuccessListener { snap ->
//Download Event Information Here
//Do stuff
GlobalScope.launch(Dispatchers.Main) {
//Download Event Images Here
val downloadEventImage = FirebaseStorage.getInstance().reference.child("Images/Events/$eventID/eventPhoto.jpeg")
.getBytes(1024 * 1024).asDeferred()
val downloadEventFounderImage = FirebaseStorage.getInstance().reference.child("Images/Users/$founderID/profilePhoto.jpeg")
.getBytes(1024 * 1024).asDeferred()
try {
val downloadedImages = mutableListOf<ByteArray>(
downloadEventImage.await(),
downloadEventFounderImage.await())
// Update stuff on UI
} catch(e: StorageException) {
// Error handling
}
}.addOnFailureListener { exception ->
// Error handling
}
}
}
What I want to do is avoid using GlobalScope but when I tried to add runBlocking to downloadEventInformationAndImages():
fun downloadEventInformationAndImages() = runBlocking {
// Do stuff
launch(Dispatchers.Main) {
it didn't work (It didn't wait Firebase to finish downloading - then I moved runBlocking to inside of function, also didn't work). How can I avoid using GlobalScope? Thanks in advance.

The best option I see here is to pass a CoroutineScope as a parameter of downloadEventInformationAndImages. So that would be
fun downloadEventInformationAndImages(scope: CoroutineScope) {
FirebaseFirestore.getInstance().collection("events").document(downloadedEventID)
.get().addOnSuccessListener { snap ->
//Download Event Information Here
//Do stuff
scope.launch(Dispatchers.Main) { ... }
}
}
The one thing you have to be careful is that every coroutine you launch here is now launched in the scope you pass in, which means that if it fails or is cancelled, it'll also cancel any parent coroutines. To understand how to deal with this, you should check the documentation for Jobs. On the other hand, you can also build your CoroutineScope with a SupervisorJob (mentioned in the documentation link above), where child coroutines fail without affecting the parents. Finally, it's also good practice to cleanup your CoroutineScope when the object that owns it reaches the end of its lifecycle. This will avoid possible memory leaks. The cleanup can be done either with scope.cancel() or scope.coroutineContext.cancelChildren(). The first one terminates the scope's job (which gets propagated to all child jobs), and the second one just cancels any child jobs that may exist. I suggest you spend some of your time just reading articles or even documentation about coroutines, because there are a lot of nuances :)

You can write your function as CoroutineScope extension function:
fun CoroutineScope.downloadEventInformationAndImages() {
...
launch(Dispatchers.Main) {
...
}
and call it from ViewModel or some other place with scope:
uiScope.downloadEventInformationAndImages()

Related

I don't understand the order that code executes when calling onAppear

I have been getting this problem now a few times when I'm coding and I think I just don't understand the way SwiftUI execute the order of the code.
I have a method in my context model that gets data from Firebase that I call in .onAppear. But the method doesn't execute the last line in the method after running the whole for loop.
And when I set breakpoints on different places it seems that the code first is just run through without making the for loop and then it returns to the method again and then does one run of the for loop and then it jumps to some other strange place and then back to the method again...
I guess I just don't get it?
Has it something to do with main/background thread? Can you help me?
Here is my code.
Part of my UI-view that calls the method getTeachersAndCoursesInSchool
VStack {
//Title
Text("Settings")
.font(.title)
Spacer()
NavigationView {
VStack {
NavigationLink {
ManageCourses()
.onAppear {
model.getTeachersAndCoursesInSchool()
}
} label: {
ZStack {
// ...
}
}
}
}
}
Here is the for-loop of my method:
//Get a reference to the teacher list of the school
let teachersInSchool = schoolColl.document("TeacherList")
//Get teacherlist document data
teachersInSchool.getDocument { docSnapshot, docError in
if docError == nil && docSnapshot != nil {
//Create temporary modelArr to append teachermodel to
var tempTeacherAndCoursesInSchoolArr = [TeacherModel]()
//Loop through all FB teachers collections in local array and get their teacherData
for name in teachersInSchoolArr {
//Get reference to each teachers data document and get the document data
schoolColl.document("Teachers").collection(name).document("Teacher data").getDocument {
teacherDataSnapshot, teacherDataError in
//check for error in getting snapshot
if teacherDataError == nil {
//Load teacher data from FB
//check for snapshot is not nil
if let teacherDataSnapshot = teacherDataSnapshot {
do {
//Set local variable to teacher data
let teacherData: TeacherModel = try teacherDataSnapshot.data(as: TeacherModel.self)
//Append teacher to total contentmodel array of teacherdata
tempTeacherAndCoursesInSchoolArr.append(teacherData)
} catch {
//Handle error
}
}
} else {
//TODO: Error in loading data, handle error
}
}
}
//Assign all teacher and their courses to contentmodel data
self.teacherAndCoursesInSchool = tempTeacherAndCoursesInSchoolArr
} else {
//TODO: handle error in fetching teacher Data
}
}
The method assigns data correctly to the tempTeacherAndCoursesInSchoolArr but the method doesn't assign the tempTeacherAndCoursesInSchoolArr to self.teacherAndCoursesInSchool in the last line. Why doesn't it do that?
Most of Firebase's API calls are asynchronous: when you ask Firestore to fetch a document for you, it needs to communicate with the backend, and - even on a fast connection - that will take some time.
To deal with this, you can use two approaches: callbacks and async/await. Both work fine, but you might find that async/await is easier to read. If you're interested in the details, check out my blog post Calling asynchronous Firebase APIs from Swift - Callbacks, Combine, and async/await | Peter Friese.
In your code snippet, you use a completion handler for handling the documents that getDocuments returns once the asynchronous call returns:
schoolColl.document("Teachers").collection(name).document("Teacher data").getDocument { teacherDataSnapshot, teacherDataError in
// ...
}
However, the code for assigning tempTeacherAndCoursesInSchoolArr to self.teacherAndCoursesInSchool is outside of the completion handler, so it will be called before the completion handler is even called.
You can fix this in a couple of ways:
Use Swift's async/await for fetching the data, and then use a Task group (see Paul's excellent article about how they work) to fetch all the teachers' data in parallel, and aggregate them once all the data has been received.
You might also want to consider using a collection group query - it seems like your data is structure in a way that should make this possible.
Generally, iterating over the elements of a collection and performing Firestore queries for each of the elements is considered a bad practice as is drags down the performance of your app, since it will perform N+1 network requests when instead it could just send one single network request (using a collection group query).

Kotlin Execution order and result issues

I'm trying to get all files from firebase's storage through listAll.
By the way..
storageReference.listAll().addOnSuccessListener { listResult ->
val image_task : FileDownloadTask
for (fileRef in listResult.items) {
fileRef.downloadUrl.addOnSuccessListener { Uri ->
image_list.add(Uri.toString())
println("size1 : " + image_list.size)
}
}
println("size2 : " + image_list.size)
}//addOnSuccessListener
enter image description here
Why is the execution order like this?
How do I solve it??
When you add a listener or callback to something, the code inside the listener will not be called until sometime later. Everything else in the current function will happen first.
You are adding listeners for each item using your for loop. No code in the listeners is running yet. Then your "size2" println call is made after the for loop. At some later time, all your listeners will fire.
If you want asynchronous code like this to be written sequentially, then you need to use coroutines. That's a huge topic, but your code would look something like this (but probably a little more involved than this if you want to properly handle errors). I'm using lifecycleScope from an Android Activity or Fragment for this example. If you're not on Android, you need to use some other CoroutineScope.
The calls to await() are an alternative to adding success and failure listeners. await() suspends the coroutine and then returns a result or throws an exception on failure.
lifecycleScope.launch {
val results = try {
storageReference.listAll().await()
} catch (e: Exception) {
println("Failed to get list: ${e.message}")
return#launch
}
val uris = try {
results.map { it.downloadUrl.await().toString() }
} catch (e: Exception) {
println("Failed to get at least one URI: ${e.message}")
return#launch
}
image_list.addAll(uris)
}
There is nothing wrong with the execution order here.
fileRef.downloadUrl.addOnSuccessListener { Uri ->
the downloadUrl is an asynchronous action which means it doesn't wait for the action to actually complete in order to move along with the code.
You receive the result with the success listener (at least in this case)
If you want to deal with it in a sequential way, look at coroutines.

WatchOS5 - how to refresh my complication instantly?

I have an apple watch complication and the iPhone app running side by side. I have a button within the app to transmit application context dictionary to the watch. I expect to see the complication title to be refreshed.
I cannot seem to force the "tap button -> see update on the complication" kind of behavior.
What is the appropriate method to force a complication update? How can I refresh my apple watch complication instantly?
I do see the title changes, but I think it requires me to tap on the complication to open it's apple watch app first. How can I get the complication to update itself on the Watch home screen?
func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: #escaping (CLKComplicationTimelineEntry?) -> Void) {
if complication.family == .graphicRectangular {
let template = CLKComplicationTemplateGraphicRectangularLargeImage()
//...configure
return template
}
}
I see this apple provided code that refreshes the complication. I'm not sure if it is too much, or if calling extendTimeline alone is sufficient if I'm generating the complication using the entry above.
func refreshComplication() {
#if os(watchOS)
let server = CLKComplicationServer.sharedInstance()
if let complications = server.activeComplications {
for complication in complications {
// Call this method sparingly. If your existing complication data is still valid,
// consider calling the extendTimeline(for:) method instead.
server.reloadTimeline(for: complication)
}
}
#endif
}
You should be able to do this by calling the refreshComplication() function from your didReceiveApplicationContext block in the file which has your WCSessionDelegate.
So if you are receiving the title via an applicationContext message your code would look something along these lines.
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
if let updatedTitle = applicationContext["updatedTitle"] {
if let title = updateTitle as? String {
//Remeber that complicationServer.swift is a seperate process therefore you will need to store the received data somehow.
UserDefaults.standard.set(title, forKey: "complicationTitle")
refreshComplication()
}
}
}
I have a setting in my iOS App that lets the user change their target and using this method refreshed the complication with the new target almost instantly. However, I believe once your complication has used up its cpu budget nothing will happen, but hopefully that is not happening for you. See https://developer.apple.com/documentation/clockkit/clkcomplicationserver/1627891-reloadtimeline
Hope that helps, let me know how you get on.
Drew

Converting a collection of Awaitables to a time-ordered AsyncGenerator in Hack

I am trying to implement Rx stream/observable merging with Hack async, and a core step is described by the title. A code version of this step would look something like this:
<?hh // strict
async function foo(Awaitable<Iterable<T>> $collection): Awaitable<void> {
$ordered_generator = async_collection_to_gen($collection) // (**)
foreach($ordered_generator await as $v) {
// do something with each awaited value in the time-order they are resolved
}
}
However, after mulling it over, I don't think I can write the starred (**) function. I've found that at some point or another, the implementations I've tried require functionality akin to JS's Promise.race, which resolves when the first of a collection Promises resolves/rejects. However, all of Hack's Awaitable collection helpers create an Awaitable of a fully resolved collection. Furthermore, Hack doesn't permit that we don't await async calls from async functions, which I've also found to be necessary.
Is it possible to anyone's knowledge?
This is possible actually! I dug around and stumbled upon a fork of asio-utilities by #jano implementing an AsyncPoll class. See PR for usage. It does exactly as I hoped.
So it turns out, there is an Awaitable called ConditionWaitHandle with succeed and fail methods* that can be invoked by any context (so long as the underlying WaitHandle hasn't expired yet), forcing the ConditionWaitHandle to resolve with the passed values.
I gave the code a hard look, and underneath it all, it works by successive Awaitable races, which ConditionWaitHandle permits. More specifically, the collection of Awaitables is compressed via AwaitAllWaitHandles (aka \HH\Asio\v) which resolves as slowly as the slowest Awaitable, then nested within a ConditionWaitHandle. Each Awaitable is awaited in an async function that triggers the common ConditionWaitHandle, concluding the race. This is repeated until the Awaitables have all resolved.
Here's a more compact implementation of a race using the same philosophy:
<?hh
function wait(int $i): Awaitable<void> {
return Race::wrap(async { await HH\Asio\usleep($i); return $i; });
}
// $wait_handle = null;
class Race {
public static ?ConditionWaitHandle $handle = null;
public static async function wrap<T>(Awaitable<T> $v): Awaitable<void> {
$ret = await $v;
$handle = self::$handle;
invariant(!is_null($handle), '');
$handle->succeed($ret);
}
}
Race::$handle = ConditionWaitHandle::create(
\HH\Asio\v(
Vector{
(wait(1))->getWaitHandle(),
(wait(1000000))->getWaitHandle()
}
)
);
printf("%d microsecond `wait` wins!", \HH\Asio\join(Race::$handle));
Very elegant solution, thanks #jano!
*(the semblance to promises/deferred intensifies)
I am curious how premature completion via ConditionWaitHandle meshes with the philosophy that all Awaitables should run to completion.

async programming in dart

I am relating to java as to how to do thread/async. I use new Thread(target).start() where target is Runnable as one way to do threading in java. New concurrent api has alternatives but we know that on specific call new threads are creating and passed in tasks are executed.
Similarly how is async done in Dart ?
I read on send/receivport, completer/future, spawnFunction. To me only spawnFunction is convincing statement that will create new thread. can one explain how completer/future help. i know they take callbacks but is there some implicit logic/rule in javascript/dart that callbacks always be execute in different thread.
below is dart snippet/pseudo code:
void callback() {
print("callback called");
}
costlyQuery(sql, void f()) {
executeSql(sql);
f();
}
costlyQuery("select * from dual", callback);
I hope my costlyQuery signature to take function as 2nd parameter is correct. so now I do not think that f() after executeSql(sql) is going to be async. may be taking above example add completer/future if that can make async to help me understand.
tl;dr: There is no implicit rule that a callback will not block.
Javascript event queue
In Javascript, there are no threads (except WebWorkers, but that's different). This means that if any part of your code blocks, the whole application is blocked. There is nothing magic about callbacks, they are just functions:
function longLoop(cb) {
var i = 1000000;
while (i--) ;
cb();
}
function callback() {
console.log("Hello world");
}
function fun() {
longLoop(callback);
console.log("Called after Hello World is printed");
}
To make something asynchronous, the callback must be put on the event queue, which only happens through some API calls:
user initiated event handlers- clicks, keyboard, mouse
API event handlers- XmlHTTPRequest callbacks, WebWorker communication
timing functions- setTimeout, setInterval
When the event is triggered, the callback is placed on the event queue to be executed when all other callbacks have finished executing. This means that no two lines of your code will ever be executing at the same time.
Futures
I assume you have at least stumbled on this post about futures. If not, it's a good read and comes straight from the horses mouth.
In Javascript, you have to do some work to make sense of asynchronous code. There's even a Javascript library called futures for doing common things, such as asynchronous loops and sequences (full disclosure, I have personally worked with the author of this library).
The notion of a future is a promise made by the function creating the future that the future will be completed at some point down the road. If you are going to make some asynchronous call, create a future, return it, and fulfill the promise when the asynchronous call finishes. From the blog post:
Future<Results> costlyQuery() {
var completer = new Completer();
database.query("SELECT * FROM giant_table", (results) {
// when complete
completer.complete(results);
});
// this returns essentially immediately,
// before query is finished
return completer.future;
}
If database.query is a blocking call, the future will be completed immediately. This example assumes that database.query is a non-blocking call (async), so the future will be fulfilled after this function exits. completer.complete will call whatever function is passed to completer.then() with the arguments specified.
Your example, modified to be idiomatic and asynchronous:
void callback() {
print("callback called");
}
costlyQuery(sql) {
var completer = new Completer();
executeSql(sql, () => completer.complete());
return completer.future;
}
costlyQuery("select * from dual").then(callback);
This is asynchronous and uses futures correctly. You could pass your own callback function into costlyQuery as you have done and call that in the callback to executeSql to achieve the same thing, but that is the Javascript way of doing it, not the Dart way.
Isolates
Isolates are similar to Java's threads, except that isolates are a concurrency model and threads are a parallelism model (see this SO question for more information about concurrency vs parallelism).
Dart is special in that the specification does not mandate that everything be run in a single thread. It only mandates that different executing contexts do not have access to the same data. Each isolate has it's own memory and can only communicate (e.g. send data) with other isolates through send/receive ports.
These ports work similarly to channels in Go if you're familiar.
An isolate is an isolated execution context that runs independently of other code. In Javascript terms, this means that it has it's own event queue. See the Dart tour for a more complete introduction to isolates.
Lets say your executeSql is a blocking function. If we don't want to wait for it to finish, we could load it into an isolate:
void callback() {
print("callback called");
}
void costlyQuery() {
port.receive((sql, reply) {
executeSql(sql);
reply.send();
});
}
main() {
var sendPort = spawnFunction(costlyQuery);
// .call does all the magic needed to communicate both directions
sendPort.call("select * from dual").then(callback);
print("Called before executeSql finishes");
}
This code creates an isolate, sends data to it then registers a callback for when it's done. Even if executeSql blocks, main() will not necessarily block.

Resources