Firebase Real-Time Database Tutorial for iOS
Learn how to use Firebase Real-Time Database to seamlessly store and fetch data in real time, while supporting offline mode and secure access. By Yusuf Tör.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Firebase Real-Time Database Tutorial for iOS
25 mins
- Getting Started
- Creating a Firebase Account and Project
- Linking the Firebase Database to Your App
- Setting Up Authentication With Firebase
- Authenticating on the Client
- Choosing a Firebase Database
- Real-Time Database
- Firestore Database
- Firebase Data Structure
- Setting Up Firebase’s Real-Time Database
- Configuring Database Rules
- Customizing the Rules
- Posting Data to the Real-Time Database
- Reading Data from the Real-Time Database
- Persisting Data Offline
- Where to Go From Here?
Posting Data to the Real-Time Database
Because you’ve added the Real-Time Database to your Firebase account, your GoogleService-Info.plist will have updated with your database URL. You’ll need to re-download it.
Return to the Firebase console. Click the cog wheel next to Project Overview in the top left and select Project settings:
Scroll down to Your apps and click the download button for GoogleService-Info.plist:
In Xcode, delete the old GoogleService-Info.plist file and drag and drop the new one into your project.
Open JournalModelController.swift. Under import SwiftUI
, add:
import FirebaseAuth
import FirebaseDatabase
Then, add the following variables below the newThoughtText
variable:
private lazy var databasePath: DatabaseReference? = {
// 1
guard let uid = Auth.auth().currentUser?.uid else {
return nil
}
// 2
let ref = Database.database()
.reference()
.child("users/\(uid)/thoughts")
return ref
}()
// 3
private let encoder = JSONEncoder()
This:
- Gets the user ID of the authenticated user.
- Returns a reference to the path in your database where you want to store data.
- Defines an encoder variable to encode JSON data.
Inside postThought()
, add the following:
// 1
guard let databasePath = databasePath else {
return
}
// 2
if newThoughtText.isEmpty {
return
}
// 3
let thought = ThoughtModel(text: newThoughtText)
do {
// 4
let data = try encoder.encode(thought)
// 5
let json = try JSONSerialization.jsonObject(with: data)
// 6
databasePath.childByAutoId()
.setValue(json)
} catch {
print("an error occurred", error)
}
This code:
- Gets the previously defined database path.
- Returns immediately if there’s no text to post to the database.
- Creates a
ThoughtModel
object from the text. - Encodes the ThoughtModel into JSON data.
- Converts the JSON data into a JSON
Dictionary
. - Writes the dictionary to the database path as a child node with an automatically generated ID.
To encode the ThoughtModel, you’ll need to make it conform to the Codable
protocol. Open ThoughtModel.swift and replace struct ThoughtModel: Identifiable {
with:
struct ThoughtModel: Identifiable, Codable {
Build and run the app. Tap the + button, write a thought and then tap Post. You won’t notice anything on the app because you haven’t added code to read from the database.
But fear not! Return to the Real-Time Database on the Firebase console. On the left navigation panel, select the Realtime Database menu item under the Build menu item. Select the Data tab under the Real-Time Database text at the top of the screen. The message you typed on the app should now display as stored in your database:
Post another message, and you’ll see it appear in real-time in the database. Now, it’s time to get that data to display in your app!
Reading Data from the Real-Time Database
To read data at a path and listen for changes, you attach an observer to the path reference to listen to events. Types of observers include:
- .value: Read and listen for changes to the entire contents of a path.
- .childAdded: Retrieve lists of items or listen for additions to a list of items.
- .childChanged: Listen for changes to the items in a list.
- .childRemoved: Listen for items removed from a list.
- .childMoved: Listen for changes to the order of items in an ordered list.
Which one you choose depends on your use case. In this app, you’ll only ever add new items to the list of thoughts. Therefore, it makes sense to use .childAdded
.
Open JournalModelController.swift. Under private let encoder = JSONEncoder()
, add:
private let decoder = JSONDecoder()
You’ll use this to decode data retrieved from the database.
Then, add the following in listenForThoughts()
:
// 1
guard let databasePath = databasePath else {
return
}
// 2
databasePath
.observe(.childAdded) { [weak self] snapshot in
// 3
guard
let self = self,
var json = snapshot.value as? [String: Any]
else {
return
}
// 4
json["id"] = snapshot.key
do {
// 5
let thoughtData = try JSONSerialization.data(withJSONObject: json)
// 6
let thought = try self.decoder.decode(ThoughtModel.self, from: thoughtData)
// 7
self.thoughts.append(thought)
} catch {
print("an error occurred", error)
}
}
That code:
- Gets the database path that was previously defined.
- Attaches a
.childAdded
observer to the database path. - When a child node gets added to the database path, it returns a
snapshot
. This contains avalue
Dictionary and akey
string, which store the data and the ID from the child node, respectively. Here, the mutable variablejson
stores the snapshot value. - The
id
key ofjson
stores thekey
variable of the snapshot. - The json Dictionary converts into a JSON Data object.
- The data decodes into a ThoughtModel object.
- The new ThoughtModel object appends to the array of
thoughts
for display on screen.
Build and run the app. You’ll see your previously saved thoughts appear!
Now, tap Sign Out. Look at the console log:
Listener at /users/5CbvKMuKArWEAFYiuTEOrwI4dxY2/thoughts
failed: permission_denied
This happens because you still have an observer attached to your database path, despite signing out. To fix this, open JournalModelController.swift and add the following in stopListening()
:
databasePath?.removeAllObservers()
This removes the observer at the database path reference before signing out. Note that this method doesn’t remove any observers at child references. So, if you were observing child nodes of this path, you’d need to call removeAllObservers()
for each reference.
Build and run again. Sign in to your account, then sign out again to confirm you no longer receive the warning in the console.
Persisting Data Offline
So your app is working. Great! But what happens if you’re at a cozy retreat nestled in the woods of a remote island with dodgy internet and you want to post a thought? You could lose your thought data! But Firebase has thought of this (no pun intended) and has a feature called Disk Persistence. This automatically caches your data to local storage while your device is offline and will save it to the database when you reconnect to the internet.
Open AppMain.swift. Inside init()
, add the following below FirebaseApp.configure()
:
Database.database().isPersistenceEnabled = true
And that’s all you need to do! Isn’t that great?
Build and run your app, but make sure to do this on a device. Sign in and then turn on airplane mode to disconnect the device from the internet. Now, post a thought.
Notice the thought displays in the list on your mobile device as if your device is online. But if you view the Real-Time Database on your computer, you see the thought isn’t stored in the database yet.
Now, turn off airplane mode to reconnect your device to the internet and watch the database. You’ll see the thought data appear like magic. Clever, eh?