[MUSIC PLAYING]
PETER FRIESE: Hello, everybody, and welcome back
to another episode in our little series
about using Firebase in SwiftUI.
So last time, we looked at how to make mapping data
from Firestore documents to [INAUDIBLE] a lot easier
by using the Codable protocol.
And at the end of the episode, I introduced
a new function called Add Book that we
wanted to use to add a new book to our Firestore instance.
But we actually didn't get around to using it,
because we didn't have any UI in place.
So today, we're going to fix that
and I'm going to show you how to implement
this little piece of UI to add a new book to our Firestore
instance.
Before we dive in, let's take a look at this screen flow
diagram to understand what we're going to do.
So here on the left hand side, we've
got our List view, which shows the list of books
that we're fetching from Firestore.
And then we've got two screens that the user can navigate to.
The first one is the Add Book screen
that we're going to implement today, which
is using the Forms API to achieve
this nice little form-like look here.
And then down here, we've got another screen
that we're going to implement next time.
And this will display the details of the book.
And from there, the user can navigate
to a screen to edit a book.
And when you look closely, you will
realize that this is actually the same screen as this one
up here.
So we need to make sure that we implement this new screen
in a reusable way.
Because adding a new book and editing
a book is kind of like the same thing.
We're dealing with a book, so there
is a lot of room for reuse.
All right, so let's go into Xcode and start building this.
So the first thing that we want to do
is add a new view model to hold our book.
So this is our book instance.
And in order to be able to subscribe
to this book in our view, we need
to make sure it is published like this.
Now let me go into our existing view model
and take this piece of code and put it into our new view model,
because that is actually a much better place for it to live in.
All right.
And with that, we can start building our UI.
So as I mentioned, we want to use the Forms API
and then have two sections, one for the book
and another one for the author.
So let's build this.
So in the sections, we want to display a couple of text fields
and then subscribe those text fields to our view model,
so let's import our view model here.
And we need to tell SwiftUI that this actually holds state.
So we could use observed object, but we
would run the risk of Swift UI recreating the view when there
is an update, and then also recreating our view model,
and we want to prevent this.
And thankfully, in SwiftUI 2, there
is a new property [INAUDIBLE],, which helps us.
And it's called @stateobject.
So this tells SwiftUI that this is actually a piece of data
that we don't want it to recreate in case
there is a view update.
So I have now created a new text field
and subscribed it to the title attribute in our book
on our view model.
And we want to have another text field
to display the number of pages.
But a number of pages is an integer,
so we need to convert back and forth
between the integer and the string that is being displayed
and edited in our text field.
And there is a convenient initializer to do that.
And it's this one, so this one.
And it uses a formatter to translate between the integer
that we store in our model and the string that is displayed
and edited in our text field.
Next, we need to wrap the entire form into a navigation view
so that we can add those buttons for saving and canceling.
And now, we can add the buttons.
And to handle the tap on the button,
we will introduce a couple of methods down here.
And what we want to do when the user taps on one
of those buttons is to dismiss the dialogue in both cases
and save the book when the user taps on Done.
So let me implement a function for dismissing the dialog
and call this from here and here.
And in order to actually be able to dismiss the dialog,
we need to tap into an environment object.
And then, down here, we can take the wrap value and dismiss it.
So in order to save the book, we need
to make a couple of changes to our view model.
So first of all, I'm going to introduce a method
that we want to call when we want to save the book.
And saving the book, actually, is pretty simple
because we have the book instance up here.
And we've got the addBook method here.
And when the user makes changes in the form,
this instance up here will be modified.
So all we need to do is call addBook with our instance
up there.
And then, with that, the book will be stored to Firestore.
So back in our view, we can call this viewModel.save.
And also make sure that we call our handle methods.
Right, so we can edit our book.
We can save it.
We can dismiss the dialogue.
So what's missing is actually invoking the dialogue.
So let's go back to our list view.
I think it would be nice to display
the dialogue in a sheet.
And we need a Boolean to manage the presentation
state of the sheet.
So let's first introduce a Boolean state variable up here.
And initially, it's false because we
don't want to initially present it
but only when the user taps on a plus button.
And now, let's [INAUDIBLE] here.
And then we need a button to actually trigger this.
So we're using an image.
And we're using the plus icon from SF symbols
to display the Plus icon.
And then, here, in the action handler,
we're just going to toggle our presentAddNewBookScreen
variable up there.
And with that, Let's see how the application looks like so far.
All right, so the button is being displayed down here,
which is not what we wanted.
And this is due to the fact that SwiftUI automatically
manages where to position your toolbar buttons.
But we have some sort of influence over that,
and we can just go in and tell it to place
the button somewhere else.
So we'll use primary action.
All right, and, with that, we can display our new screen.
And let me bring up the Firebase console
so we can see what happens when I add a new book.
So how would you like if I wrote "SwiftUI in Action"?
And let's say 200 pages, and then hit the Done button
to save it.
And there we have it.
We have a new Firestore document,
which represents our book.
So if you're wondering why there is a zero,
this is due to the fact that SwiftUI
is very economic with triggering the format of our number field.
So if we go back in, so whenever I type something here,
it will not trigger the formatter,
only if I hit the Enter key.
So let's try this again and add another book, let's say,
250 pages.
I'll hit the Enter key and then press Done to save it.
And now, let's take a look what happened.
And we can see that it stored 250 in our document
because we hit the Enter key to trigger the number formatter.
I think they want to be economic and make sure that they are not
wasting processor cycles while the user is
typing, which is fair enough.
But at the same time, I don't think
that's great user experience because you
have to think about hitting the Enter key once you've
added a number.
And that's probably not what most users would expect.
So we'll see if that stays like it is.
But one thing that I did want to show you,
which we can influence, is this thing.
When I go into this screen, you'll
notice that the Done button is enabled all of the time, which
means that, if I don't enter any information about the book,
I can hit the Done button.
And then we will have an empty document here.
And that's not what we want.
Instead, I'd rather have the Done button
be disabled until we have entered information.
So let's go into our code and make a couple of changes
to implement this.
And this is actually a thing that we
can do in our view model.
And to do that, we are going to use Combine.
So let's first to import Combine and then
add a new Published variable to our view model
to track the modification status.
So it's going to be Published.
And we call it modified.
And initially, it's false.
And the best place to initialize our Combine pipeline
is in the initializer.
And now, we can go ahead and implement the pipeline.
So the first thing we need to do is drop the first event
because the first event that happens
is when we assign the book up here.
And if we reacted to this event, we
would end up in an endless loop, which is not what we want.
So after we have dropped the first event,
we can actually take care of all the other events
that we actually want to use and use the sync operator here.
And what we will receive is a book.
And once we receive a book, that means that there
has been a change to the book.
So maybe the user has edit the number of pages
or they have modified the title and reviewed.
They have made one change, any change, to the book.
And we'll just say, OK, in that case,
we will change the modification state
and say it is now modified.
I'm going actually make--
let me make this a weak, self.
And then we need to take care of the result of that
and store this in a cancellable.
And then in our view, we can go in and then
attach this modifier to our button, which drives
the enabled and disabled state.
So we will say, if the view model is modified,
then we want to hide this button.
Or we, rather, want to disable the button.
All right, so let's try this.
And as you can see, it's grayed out at the moment.
And once I start typing, it will become enabled, which is great.
But there is a little flaw in our implementation.
So if I go back and undo my changes,
it will still be enabled.
And this is due to the fact that we're
using a rather simple implementation here.
So this, obviously, is a rather naive implementation.
And we should improve this.
And actually, this is my challenge to you.
So I challenge you to implement a better version
of this Combine pipeline.
So go ahead, [INAUDIBLE] the repository for this video,
and then go in, implement a better version of that.
Send me a pull request, or send me a tweet on Twitter,
and let me know what your implementation is.
And then we can see who's got the best implementation.
And keep in mind that we also want
to use the screen down here.
So when the user chooses a book to have a look at the details
and then edit the book, and then they
start typing and making changes to the book,
and then make up their mind and undo the changes,
then, obviously, the modification state
should go back to unmodified to drive the Done button
and disable the Done button.
And yeah, I'm looking forward to your implementations.
And with that, we're at the end of this week's episode.
I hope you learned something.
And if you've got any questions, feel
free to drop them in the comments below.
And if you liked the video, please be sure to like it.
Thanks for watching, and I will see you next time.
[MUSIC PLAYING]