In this tutorial, you’ll learn how to manage data in a Firebase Firestore database.
For some background on Firebase and instructions on how to get your Firestore database set up, please read my earlier Introduction to Firebase post.
Data, Documents, and Collections
Working with Firestore, data, documents and collections are the key concepts which we need to understand.
Data
Data can be any of the following types:
- String
- Number
- Boolean
- Map
- Array
- Null
- Timestamp
- Geopoint
- Reference
Documents
Documents contain data items in a set of key-value pairs. Firebase Firestore is optimised for storing large collections of documents. A document is a lightweight record with fields which map to values. Each document is identified by a name.
For example, we could describe a Country
document as follows:
Country Name: "Canada" Capital: "Ottawa"
Here, Country
is the document which contains two fields: Name
and Capital
.
Collections
Collections contain sets of documents. Documents live in collections, which are simply containers for documents. For example, you could have a Countries
collection to contain your various countries, each represented by a document:
Countries Country Name: “India” Capital: “New Delhi” Country Name: “England” Capital: “London"
In short, data is part of a document, which belongs to a collection.
We’ll learn more about data, documents and collections by building a sample app.
Creating a New Firebase App
To start, you’ll need to create a new Xcode project and set it up in the Firebase Firestore portal.
First, create a project in Xcode and name it ToDoApp. Then, create a corresponding project in Firebase and also name it ToDoApp.
Next, register your app with a bundle identifier.
Firebase will create a config file for you to include in your project. Download the config file and add it to the root of your Xcode project as shown.
Here’s a screenshot of my project folder structure with the GoogleService config file highlighted.
Next, you need to install CocoaPods for Firebase. CocoaPods make it easy to install and manage external dependencies such as third-party libraries or frameworks in your Xcode projects. If you don’t have a Podfile already, follow the instructions to create one. Then add the Firebase core services to your Podfile and run pod install
.
Next, the Firebase setup wizard will give you some code to add to your AppDelegate
. Copy the highlighted lines below into your AppDelegate file.
When you’ve completed all these steps, the project setup screen in Firebase will look like this:
To complete the installation, you need to get your app to communicate with the Firebase server. Simply run your Xcode project and go back to the project setup screen in Firebase, and you should see something like this:
Handling Data in a Firebase App
Now that we are done with the initial setup, let’s start handling data in our app.
In this section of the tutorial, we’ll create an app to edit, delete and read data about tasks. But before we start adding or creating tasks, we need to think about the fields we would require for a task. Since this is a simple app, we’ll stick with a single field:
-
task_details
: text describing the task
We also need a collection, which we’ll name tasks
, and it will contain these Task
documents. This is how the structure will look:
tasks Task task_details Task task_details
To represent this data, let’s create a simple task model class in TaskModel.swift with the code below:
import Foundation public struct TaskModelDetails { var taskDetails: String init(taskDetails: String){ self.taskDetails = taskDetails } } class TaskModel { var task: TaskModelDetails init(task: TaskModelDetails) { self.task = TaskModelDetails(taskDetails: task.taskDetails) } }
Reading Tasks From the Collection
We will be displaying our tasks in a simple table view.
First, let’s create a ToDoListViewController.swift class. Then, add ToDoListViewController
in Main.storyboard and connect it to ToDoListViewController.swift. Add a UITableView
to ToDoListViewController
in the storyboard and implement delegates.
Now we’ll create ListService.swift to hold the code to retrieve task data. Start by importing FirebaseFirestore
. Then create a Firestore instance withFirestore.firestore()
. ListService.swift should look as follows:
import Foundation import FirebaseFirestore class ListService { let db = Firestore.firestore() }
Next, we’ll add the completeList
method in the ListService
class. We’ll get all the data and documents from the tasks
collection by using the getDocuments()
method from Firebase. This will return either data or an error.
func completeList(completion: @escaping (Bool, [TaskModel]) -> ()){ db.collection("tasks").getDocuments() { (querySnapshot, err) in } }
In this code, querySnapshot
contains the returned data. It will be nil if there is no collection with the name tasks
.
Here is the rest of the completeList
function: it retrieves the task data and calls the completion callback with any retrieved data.
func completeList(completion: @escaping (Bool, [TaskModel]) -> ()){ var tasks = [TaskModel]() db.collection("tasks").getDocuments() { (querySnapshot, err) in if let err = err { print("Error getting documents: (err)") completion(false, tasks) } else { for document in querySnapshot!.documents { print("(document.documentID) => (document.data())") } completion(true, tasks) } } }
Let’s try it out! Use the code below to call completeList
from ToDoListViewController
.
import Foundation import UIKit class ToDoListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIGestureRecognizerDelegate{ let taskService = ListService() override func viewDidLoad() { super.viewDidLoad() getAllTasks() } func getAllTasks() { taskService.completeList(completion: { (status, tasks) in print(status) }) } }
There should be no errors, but querySnapshot
will be nil since we do not have any documents in our collection yet. In the next section, we’ll add some data and then try calling the completeList
method again.
Adding Tasks to the Collection
Let’s create an addToList
method in ListService()
.
func addToList(taskDescription: String, completion: @escaping (Bool) -> ()) { completion(false) }
We’ll use the passed-in task description to create a taskData
instance and add it to the tasks
collection. To do this, we will use the addDocument
method from Firestore Firebase.
func addToList(taskDescription: String, completion: @escaping (Bool) -> ()) { var ref: DocumentReference? = nil ref = db.collection("tasks").addDocument(data: [ "task_details": taskDescription, "task_id" : "", ]) { err in if let err = err { print("Error adding document: (err)") completion(false) } else { print("Document added with ID: (ref!.documentID)") completion(true) } } }
If there is no error, we’ll print the document id and return true in our completion block. task_id
will be the document id. For now, we’ll just send the empty string, but eventually we’ll update the task_id
with the correct document id.
Let’s add some test data by calling addToList
from ViewController
. After running the below code, go back to the console and you’ll see that an entry was added. Now, running getAllTasks()
should return true
and a tasks count of 1.
import Foundation import UIKit class ToDoListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIGestureRecognizerDelegate{ let taskService = ListService() override func viewDidLoad() { super.viewDidLoad() addTask(taskText: "Buy Grocery") getAllTasks() } func addTask(taskText: String) { taskService.addToList(taskDescription: text, completion: { (status) in if status { print("Status", status) } }) } func getAllTasks() { taskService.completeList(completion: { (status, tasks) in print(tasks.count) }) } }
Of course, in a real app, you’d want to create a user interface for adding tasks!
Deleting a Task Document in Firestore
Finally, let’s see how we can delete a document in Firestore. Go back to the Firebase Firestore console and make a note of the document id value for “Buy Groceries“. We will be deleting the document based on the document id.
Now, add a deleteFromList
method to our ListService
as shown below.
func deleteFromList(taskId: String, completion: @escaping (Bool) -> ()){ db.collection("tasks").document(taskId).delete() { err in if let err = err { print("Error removing document: (err)") completion(false) } else { completion(true) } } }
Let’s call deleteFromList
from ToDoListViewController
. We’ll pass it the document id of “Buy Groceries”, which we copied from the Firebase console.
import Foundation import UIKit class ToDoListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIGestureRecognizerDelegate{ let taskService = ListService() override func viewDidLoad() { super.viewDidLoad() deleteTask(taskId: "ddiqw8bcnalkfhcavr") } func deleteTask(taskId: String) { taskService.deletFromList(taskId: taskId, completion: { (status) in print(status) }) } }
Running the above code, you should see status
as true
. If we go to the Firebase Firestore console, we should see that the “Buy Groceries” task has been deleted.
Conclusion
With that, you’ve learned how to create, read and delete data from a Firebase Firestore database. In this tutorial, we built an app to manage tasks using Xcode and Firebase. Hopefully, this has given you some new skills which you’ll be able to put into practice in your upcoming projects.
To make this app more functional, you could implement table view delegate methods to display the tasks in your database, a UITextField
to add new tasks, and a swipe gesture to delete tasks. Try it out!
If you have any questions, let me know in the comments below.