SPEAKER: Hey, everyone.
Almost all of Firebase's APIs are asynchronous.
So you might be wondering, what does that even mean?
And how do you call them?
So today, I'm going to show you three different ways how
you can call Filebase's APIs asynchronously from your Swift
code and why this is important.
So we're going to look at callbacks, at combine,
and async await.
So let's get started.
So I have decided to use the Firebase authentication
SDK to explain this.
And we have a sign in form that captures the user's email
and password.
And then we're using the sign in with email and password API
to sign the user in.
So we've got the text field to capture their email address,
a secure field for the password, and then the button
to handle the login process.
And I'm using a view model to capture all those pieces
of information.
And when I tap here, we will be taken into the view model.
You can see that we've got the published properties for email
and password.
And then this one here is important, isSignedIn.
We will set this to true if the user has successfully
signed in.
All right.
So let's start implementing this login function here.
And I'm going to do so by first getting the auth object.
And that has a function which is called signIn
with email and password.
So let's use this.
And then we can use the user's email address
and their password.
And then the next parameter is the completion parameter.
And that is a callback that will be
called when the user has successfully signed in
or if there has been an error.
So when I tap the Enter key, Xcode
will turn this into a closure for me.
And this has two parameters, the first one
being the authDataResult and the second one being the error.
And let's first start by doing some error handling.
So if let error equals error.
And then we want to assign this to the error message field.
And this will be displayed on the UI,
so error.localizedDescription.
And then we can return from here because there
is no point in trying to continue
if there has been an error.
All right.
Next up, we will want to extract the user
from the authDataResult. And I'm going to use a guard
statement to do that.
And if we don't get a user, so if it's nil,
then we want to display another error message.
For example, no user.
And then we need to return as well.
So at this point, we can be pretty sure
that we do have a user because a guard statement guarantees
that the user is not nil.
And because that's the case, we can basically
just say isSignedIn is true and the user is signed in.
And also let me add a little print statement here.
User has signed in as and then we'll print their email address
like so.
All right.
And because we are in a closure, we
will need to use self to set this property.
And email might be nil so we'll use the nil coalescing operator
and print an empty string if that's the case.
Let's run this application and see it in action.
So I've already connected this application
to your Firebase project.
And I've created a test user that we're
going to use to sign in.
So now the UI says, "Hello.
It looks like you're not signed in.
Tap here to sign in."
And then I'm going to provide the email address of the test
user, test@test.com.
And then tap the Sign In button.
And the UI says, "Hello, you're signed in as test@test.com."
And you saw in the console that our print statement here
has printed the user's name.
Now where has it gone?
There we are.
All right.
So that's the first way and probably the most common way
to handle asynchronous processing.
You will just use a callback closure.
So let's next look at a different way to do that.
And I like to show you how to use Combine.
So Combine is Apple's reactive framework for processing
events over time and what that really means
is that, for example, UI elements, when the user starts
typing something into the text field, they will emit events.
And you can basically set up a processing pipeline
and handle those events as they come in.
And similarly for an API like Firebase authentication
where you sent in user and password and, in response,
you get a result, that is an event.
And you can handle this event using Combine and process it.
Let's take a look at how this works.
So what I'm first going to do is I'm
going to delete this closure because we don't need this
anymore.
And I can leave the rest of the call in place
because, as you will see, this doesn't have to change.
We can basically just use this in all different ways
to call asynchronous APIs.
So let me scroll through the top of the file
to show you that I also have already imported Combine
and FirebaseAuthCombineSwift.
And this is the combined support we implemented for Firebase.
So let's go back again.
And now if I press option and click on this,
you will see that this function also
has a return value of future.
And this is a sign that I can use this using Combine.
In the future, you can see that I get the same types back
that I did in the callback closure.
So I get an authDataResult and I get an error.
All right.
So let's take this.
And what we want to do is we want
to map this and extract the user object from the authDataResult.
All right.
So let me use the map operator.
And you can see here in the code completion,
Swift has detected that we get a return value of authDataResult
and error.
So we're going to map this and extract the user
object from the authDataResult.
So let me turn this into a parameter and then
inside here, authDataResult.user.
So basically, I'm just grabbing the user
and returning it out of the map function.
So the next step is, in case something goes wrong,
we might get an error.
So for now, I'm just going to replace any errors
with nil like this.
And in the next step, I either have nil or I have a user.
So I can use the map operator again.
And then see if the user is not equals to nil,
and if it is nil, it means that we weren't able to sign in.
If it's not nil, it means that we
have a user so the user has signed in.
And then in the last and final step,
we will assign this Boolean to the isSignedIn property.
Let's run this now and see if this still works.
So we're still signed in so let me first
sign out and then sign in again using the same user
information.
And we signed in correctly.
So you can see we signed in as user.
And that's the user ID.
All right.
So this is how you use Combine.
And let me actually make this a little bit simpler
so you know that instead of using a name parameter
we can use an anonymous parameter.
So we can replace this by saying $0 and the same down here, $0.
so that's basically the first parameter.
And by doing so, it becomes even more compact.
All right.
So that's how you use Combine to call asynchronous APIs.
Now let me show you the third and last one.
Let's use Async Await.
So Async Await is a feature that Apple shipped with Swift 5.5.
And it allows you to call asynchronous APIs
in a much more comfortable and much more linear way.
So if you remember how the closure looked like that we
implemented for callbacks, it was a little bit complicated
and pretty nested.
And you will see in a minute that when
we're using Async Await, this will become a lot less nested.
And we'll have a much more linear flow of code.
So let me first get rid of all of this.
And as before, we can just continue using this here.
And what I want to do is I'll just
say I'm going to call this piece of code using await.
And the compiler will jump in and say, well,
you know what, we are not in an asynchronous context
so you can't just say you want to call this using await.
So let's use the compiler and actually use
Xcode to fix this error.
So it added the async keyword to our function
to basically say, well, no, this function is asynchronous.
And now we have another error because sign in might throw.
So you see that the signature says sign
in with email and password.
Async froze so we need to handle the exception.
And to do that, we'll just add try.
And then we will need to wrap this in a do catch block.
And then we will just print the error.
So now that we handled the error,
we can try to capture the result of this sign in function.
Let's look at the signature again.
So now instead of having a closure,
we receive the authDataResult as the return value
from this function, which is really neat.
So what we can do is we can say let authDataResult
equals try await.
And in the next step, we'll just extract the user
from the authDataResult. And now that we have the user,
We also know that we're signed in so we can set the isSignedIn
property.
OK.
And let's do a print statement.
All right.
So this should make the compiler happy.
All right.
Cool.
So when I now try to run this, I will get another error message
because we are trying to call this
from a nonasynchronous context.
So the compiler says async call in a function that
does not support concurrency.
And this is because the onSubmit you modify here
is not asynchronous.
But we can just wrap our call in a task, and by doing so,
opening up an asynchronous context here.
And then let's not forget to actually prefix
this with await.
So we open the asynchronous context,
and then we await the call to viewModel.login().
And we'll need to do the same down there.
So let me just copy this piece of code, paste it here.
And now we should be able to run this.
We're still signed in.
Let me sign out and sign in again.
And now when I have filled in the information--
test@test.com--
we sign in using async await.
One more thing that we need to take care of--
so in the console, you will see this error message,
"Publishing changes from background threads
is not allowed.
Make sure to publish values from the main thread via operators
like receive on onModel updates.
So let's go back into our view model.
You can see the same error message here in purple.
And this indicates that when accessing self.isSignedIn,
we are trying to access something which has
an impact on the main thread.
So isSignedIn is a published property.
And we are using this in our UI to drive a state change.
And because any changes on the UI happen on the main thread,
this is a problem.
Because the authentication call here,
that might return on a background thread.
And we are now trying to access the foreground of the UI
from a background thread.
And that's not something that we should be doing.
And the easiest way to fix that is to actually use
the main actor annotation, or attribute,
on this function to tell Swift, please execute the login
function on the main thread.
So now when I run this again, this should be fine.
And we should no longer receive this warning.
OK, let me sign out and sign in again.
And there you go.
So we signed in, and no warning, no error message.
Everything is clean because we told Swift to execute
this on the main thread.
Cool.
So there you have it, three different ways
how to call asynchronous APIs in Firebase from your Swift code.
And now you might be wondering which one to use.
And it's largely a matter of personal taste or preference.
So since Async Await is now available
starting with Swift 5.5 and it can be back-ported to iOS 13,
you might start using that if your code base can be compiled
using the Swift 5.5 and if you are OK with supporting
iOS 13 and upwards.
If you need to support an older version of iOS,
or if you can't yet use Async Await for reasons,
then you can use either combine, or use the callback approach,
or you can use a combination of all of them.
So if you're using callback approach in your code base
and you want to migrate to Async Await,
you can do so if you want to combine
Combine and Async Await.
That's fine as well.
Personally, I think Combine is a great way
to implement UI-related functionality,
whereas Async Await is probably more suited for anything that
has to deal with your back end.
But as I said, it's a matter of taste.
All right, thanks for watching.
And I will see you in the next one.
[MUSIC PLAYING]