TODD KERPELMAN: Hey there, iOS developers.
Interested in getting started with Cloud
Firestore, the hot new database in the cloud for mobile apps?
Hey, me too.
Wow, what are the odds we'd meet together in a video like this?
Well, let's work together to save our data to the cloud
on this episode of Firecasts.
[TECHNO MUSIC PLAYING]
So I'm working on a little app here
that lets me store an inspirational quote.
I mean, I can't just be expected to start doing work, right?
I need an inspiring quote, preferably
from Einstein or maybe Marilyn Monroe to motivate me.
And what better way to do this than with an app.
So my app here consists of a text field,
where I can enter my inspirational quote,
and another one, where I can save the author's name.
And then, we've got a save button,
where we're going to save whatever
inspiring message our user has entered to the cloud.
Later on, we'll load whatever data our user
has saved to the cloud and display it at this big label
here at the top, so we can see the latest inspirational quote
that we've entered.
So if you want to follow along, go ahead
and create your own version of this app.
It really is nothing more than these UI elements here.
Just make sure you have some IBOutlets for the text
fields and the label and create an IBAction for the button
called Save Tapped.
Now, I've already gone ahead and created a Firebase project
in the Firebase Console, installed Firebase
using CocoaPods, copied over my configuration file
to my new XE Workspace, and initialized the Firebase
library.
If you don't know what I'm talking about,
check out this Getting Started video here
and the link in the description below, and then come on back.
It's OK.
I'll wait.
All right, next, we need to make sure we install Firestore.
So I'm going to go into my pod file here
and make sure I've added a line for pod Firebase/Firestore.
And then, I'll save that and run pod install
to install the new library.
So I can open up my XE Workspace and I am good to go.
Now before we go ahead and write some data,
let's take a moment to understand how Cloud Firestore
data generally works.
Frankly, this could be the subject
of its own separate video.
And it probably will be one day.
But this here's the executive summary.
So Cloud Firestore is a document database.
That means it stores your data in a big tree-like structure,
kind of like the original Realtime Database.
But everything is placed into documents and collections.
Now, you can think of a document as something like a dictionary.
It's got key value pairs, which the Firestore folks like
to refer to as fields.
And the values of these fields can be any number of things,
from strings, to numbers, to binary values,
to little JSON-y looking objects that the team likes
to call maps.
And collections are basically, well, collections of documents.
Now, there are a few rules around the use of these things.
The first is that collections can only
contain documents, nothing else, no collections of strings,
or binary blobs, or anything else here.
Second is that documents can't contain other documents.
But they can point to subcollections.
So it's very common to see a collection containing
a bunch of documents, which then point to subcollections
that contain other documents, and so on and so forth.
In a workout app for instance, I might have a users collection.
And this collection would contain
a document for each user.
And then, each of these documents
might point to a workout subcollection.
That, in turn, contains a document
for each different type of workout the user has performed.
And I don't know.
These might contain a history subcollection
that keeps track of every time the user performed
one of these workouts, along with their average heart
rate or other data.
Now, if you're coming from the Realtime Database land,
this kind of deep nesting might be giving you
heart palpitations.
But calm down, don't worry.
This kind of data structure is completely normal in the Cloud
Firestore world, where queries are shallow,
meaning that, when you grab data from a document,
you'll grab just that document and not any of the documents
contained in any of these subcollections, which is nice.
The third rule is that the root of your database
can only consist of collections.
In a normal production app, this is
going to feel pretty natural.
You're going to have your collection of items,
and your collection of users, and your collection of games,
or what have you.
The one time it's going to seem kind of weird?
When you're creating a little test app like ours and
you just want to save two strings.
So looking at our app, at the top level,
I'm going to start with a collection
that I'm calling sampleData.
This will then contain one single document
called inspiration.
And this document will itself have two key value
pairs, or fields, one called quote and another called
author.
Incidentally, note that this is basically
a global document that I'm altering, meaning I'm
not storing one quote per user.
I'm just letting everybody in the world
alter this one document.
If I were looking to save a different quote per user,
I'd probably be setting up a users collection
and creating a different document for each user.
Make sense?
All right, let's start building.
So first off, in my view controller,
I'll make sure to import Firebase.
Remember, that's all I need to import, no matter
what specific Firebase libraries I'm using.
Next, in my Save Tapped call, I'll
grab the text from my text fields
and maybe check to make sure that they aren't empty.
Do that for both of these, OK.
Now, I'm going to create the data
I want to save in my document.
This will be a dictionary of type string any.
And I'll use the key "quote."
Any text field value is my value.
And then, we'll do something similar for the author.
And yes, if this were a real application,
I wouldn't hard code the key names like that.
I'd probably be using constants.
OK now, I just need to save this to the cloud.
And to do that, I need to specify in which document
I want to put this data.
And we do that through an object known as a document reference.
Now, it turns out I'll be using this same reference
multiple times throughout this project.
So I'm going to make it a property here in my class.
And then, I'll set it down here in my [? view did load. ?]
Now, there are a few ways of setting my document reference.
First, I could call Firestore.collection
sampleData.documentinspiration.
Now, I kind of like doing this because it
serves as a nice reminder that you're always
going to alternate between collections and documents.
But it does start to look a little verbose and weird,
when you get several layers deep.
So an alternative that I sometimes prefer
is calling Firestore.document, where
I pass along a slash separated string as our path.
In our case, the path would be sampleData/inspiration.
Just remember that, in your path,
you're always going to be alternating
between collections, documents, collection, document,
and so on.
So now that I have specified my document, let's go back
to my Save Tapped method, where I'm going to call setData
on it, which will take in the data
I created above as the contents of my document.
Now, this will replace my current document if it exists,
and it will create it if it doesn't.
It also conveniently creates the sample data collection too.
I don't need to worry about whether or not
that thing exists.
Then, in my completion handler, I'm
just going to do some basic error checking.
And well, that's about it.
Let's give it a try.
So I'm going to start up my app.
I'll enter "Don't be a slacker," here in the text field.
And I'm pretty sure that was said by Sir Isaac Newton.
So I'll hit save.
And well, it looks like I'm getting an authorization error.
And this is because, just like the Realtime Database,
my Cloud Firestore implementation
contains a set of security rules that determine whether or not
a certain action is permitted.
And at the time of this video, by default,
they're set up so that nobody can read or write
to the database.
So the proper solution here would probably
be to add in some sign-in using Firebase Auth,
and then create some proper, well thought
out security rules, based on what information I'm
willing to share to each user.
That said, all that would make this a much longer video
than it already is.
So I'm going to do a little bit of a hack
here and make my sample data documents open to the public.
So let's head on over to the Firebase Console.
I'll select my project, go to the database section,
and make sure I've got Cloud Firestore selected here
for my database options.
Then, I will click on the Rules tab.
And I'll add these lines here to allow
reading and writing to anything that's part of my sample data
collection.
Like I said, this is probably a pretty terrible idea
from a security perspective.
But at least I've contained the damage
to just what's in my sample data.
So I'll publish that.
And we're done.
So I'm going to wait a minute, and then give it another try.
I'll go back into my app, hit Save, and, all right,
looks like it worked.
And I can verify this by going back to the Firebase Console.
We'll select the Data tab.
I'm going to look at our sample data collection,
find our inspiration document.
And sure enough, it looks like we have saved our inspiring
quote to the world.
Woohoo!
OK, next up, we need to show our users their inspirational quote
by grabbing that data and populating our label with it.
Now, like the Realtime Database, Cloud Firestore
lets me listen to changes in the database
and update my app in real time.
But I know that, for some of you out there,
the idea of creating a real-time listener for all your data
is weird and strange.
And sometimes, you just want a basic fetch call to get it,
right?
So I'm going to show you how to do that first.
And then, we can talk about getting fancy
with some real-time listeners.
So I'm going to create a Fetch button.
I'll add a few constraints and create an IBAction for it.
And we'll call it, oh, Fetch Tapped.
OK, let's edit this method.
So I already have my document reference.
As you'll recall, I created it as a property earlier.
So I'll simply call getDocument on that reference
to fetch the document.
This comes with a completion handler,
which I will make a trailing closure, like all the cool kids
are doing these days.
And the two arguments passed in here
are a document snapshot and an error.
Now, a document snapshot is basically an object
that represents your document.
You can find out its ID, read some metadata about it,
make sure the underlying document it represents really
exists, and, most importantly, grab the data
that it contains by calling data on it.
So first, we'll see if our document exists.
And if it does, we will call data on it
to extract the contents of that document
as a string any kind of dictionary.
And yeah, I should probably add in some error checking, too.
But for brevity's sake, I won't.
Next, I'll get our quote by grabbing data quote.
Since grabbing contents from your data
like this will always be an optional value,
I'm going to use the double question mark
operator to specify a default value here,
in case this is nil.
And I'll do the same thing for my author name.
And now, we can do some fancy string interpolation
to display this in my label.
Incidentally, you might be asking yourself,
hey, don't we need a weak reference here?
But it turns out we don't really because this block isn't
retained beyond the fetch completion.
So there's little risk of a retain cycle here.
So we're going to run it.
And there we go.
I can now save my inspirational quote to the cloud,
and then load it with a click of a button.
And it shows up in my label.
This is great.
We've got our data successfully saved and loaded
from Cloud Firestore.
But what if you are interested in getting
your data in real time?
What if this explicit fetching seems quaint and old-fashioned
to you?
Well, let's show you how to get your data in real time, too.
Basically, the process is going to work nearly the same.
Let's override viewWillAppear.
And in there, I'm going to bring up my doc ref.
But then, instead of calling getDocument,
I'm going to call addSnapshotListener.
And this will fetch my document the first time I call it.
But then, it will also grab updated data
any time thereafter, when my data changes.
This call has a completion handler with a document
snapshot and an error, kind of just like my getDocument call.
So in fact, I'm going to copy the exact same code from my
get call into here to update my label.
And well, that's actually it.
So you might be asking yourself, hey,
don't we need a weak reference this time around,
since this snapshot listener is going
to stay around for a while?
And it turns out, the answer is still no, as long
as we do the right thing and turn off these listeners
some time before our view controller is deallocated.
That's not only good for memory, but it also
helps to keep your network usage low,
which means better battery life for your user
and potentially lower database costs for you.
So I'm going to create a little quote listener property here
to track that listener that I created in my viewWillAppear
call.
Then, in my viewWillDisappear method,
I can just call remove on that quote listener reference.
And there we go.
That will keep my app nice and efficient,
by only listening for data when this view is on screen.
So let me run the app again.
You can see that, thanks to my snapshot listener,
my label automatically gets updated
with the inspirational Newton quote that was used previously.
And then, if I change my inspirational
quote to this one--
I'm pretty sure this was Gandhi who said it--
you can see that my label gets updated automatically
without my having to even touch that Fetch button.
Wow, gee, that was fast.
Maybe too fast, right?
It looked like my label got updated
before I even got that console message that my data was
saved to the cloud.
How'd that happen?
Well, what's happening is that Cloud Firestore
is making my app run as speedy as possible by notifying me
of changes that happened locally as if they
had happened on the server.
But in the meantime, it's still going ahead
and updating that data remotely in the background.
Now, my client still receives an update from Firestore
that this data has been changed on the server.
But by default, the Firestore library will ignore this event.
Because with the exception of a little bit of metadata
that says, yes, this is really from the server,
it's basically identical to the first.
Now if you want, you can also get notified by the [? SPK ?]
about this second event, the one that
happens when the write actually occurs on the server.
And you can do this by adding some options to your document
listener that say, yes, notify me
even when just the metadata changes, like so.
And so when I do that, this callback will get
called twice, once for the change in my local cache,
and then once for the server.
And there may be times when this behavior is what you want.
But most of the time, honestly, this default behavior
works just fine.
So I'm going to go back to that.
And there you go.
Congratulations, you are now saving your data
in one of the most powerful and sophisticated online databases
in the world and using it to store two lines of text, which
maybe is a little overkill.
But in a real app, you'll appreciate this power,
trust me.
Now, there's a lot more to learn about Cloud Firestore,
including more details on how that data is structured,
how subcollections work, and how to run
some more sophisticated queries, all of which
are great topics for future videos.
In the meantime, go back and change those security rules
so they're a little more secure.
And then when you're done with that,
be sure to check out the documentation
and try using Cloud Firestore in your own app.
As the saying goes, the journey of 1,000 miles
begins with reading the documentation.
I'm pretty sure Miles Davis said that.
[TECHNO MUSIC PLAYING]