Google’s Firebase platform includes the Real-Time Database. This is a fantastic way to coordinate quickly changing data to your application. The beauty of using this mechanism is the fact that when a change is made to your data, a notification is sent to your application to update the relevant piece of data.
There are two main advantages of using this push system as opposed to polling the data. Firstly, the changes to the database are sent and displayed on your device almost immediately. The second advantage is that this system doesn’t require downloading all of the data in the list multiple times, but only the data that has changed when it changes.
Tutorial:
You can start with a test project that I have already prepared, it has all of the Firebase frameworks that you will require already set up using Cocoapods. This will save you a bit of time and enable me to focus on the more important aspects of Firebases Real-Time Database. The test project is available here:
https://github.com/rtking1993/FirebaseTest
Now we should be starting with a single view application template with Firebase frameworks set up and ready. The first thing we should do is get the outlets properly connected to our MainViewController file. We will need a UIView, UIButton, UITextField and a UITableView. Place these onto your storyboard, similarly to what I have done below.
Next, we want to connect these objects up to some outlets, which should look something like this in your MainViewController.swift file.
We are going to be displaying a list of our items that are going to be connected to the Firebase Database. So we need to have a local instance of our items. Our items are going to be a simple model. Our model will look like this.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import FirebaseDatabase | |
// MARK: Item | |
enum ItemParameters: String { | |
case item = "item" | |
} | |
// MARK: Item | |
struct Item { | |
// MARK: Constants | |
let identifier: String | |
var item: String | |
let ref: DatabaseReference? | |
// MARK: Init Methods | |
init(identifier: String = "", item: String) { | |
self.identifier = identifier | |
self.item = item | |
self.ref = nil | |
} | |
init?(snapshot: DataSnapshot) { | |
identifier = snapshot.key | |
guard let snapshotValue = snapshot.value as? [String: AnyObject], | |
let item = snapshotValue[ItemParameters.item.rawValue] as? String else { | |
return nil | |
} | |
self.item = item | |
ref = snapshot.ref | |
} | |
// MARK: Helper Methods | |
func toAnyObject() -> Any { | |
return [ | |
ItemParameters.item.rawValue: item | |
] | |
} | |
} |
This model will give us the ability to instantiate itself, either using regular parameters or a DataSnapshot that we will receive from the firebase server.
Now we can head over to our MainViewController.swift file and add an array of items. We are going to use a property observer called didSet on our array. The didSet property observer will be fired whenever the items array is set, we can put the code here to reload our table view. This way our UITableView will reload every time our items array is set.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MARK: Variables | |
var items: [Item] = [] { | |
didSet { | |
myTableView.reloadData() | |
} | |
} |
Now that we have an array of items, we can fill our data source methods on our UITableView. If we do this as an extension of MainViewController outside of our class, we can keep the code nice and tidy. Just like the below example.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MARK: UITableViewDataSource Methods | |
extension MainViewController: UITableViewDataSource { | |
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { | |
return items.count | |
} | |
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { | |
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "Cell") | |
let item = items[indexPath.row] | |
cell.textLabel?.text = item.item | |
return cell | |
} | |
} |
Now we are going to create this class called ItemsRemote. It will allow us to remove networking code out of our MainViewController. It will have methods to post an item to the Database and get all of our items from the Database. Having this class improves the readability of the ViewController and also enables us to have a constant DatabaseReference point for our list of items, this is important so we know we are always reading and writing from the same place.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MARK: Frameworks | |
import Foundation | |
import FirebaseDatabase | |
// MARK: ItemsRemote | |
class ItemsRemote { | |
// MARK: Constants | |
static let itemsReference = Database.database().reference().child("items") | |
// MARK: POST Methods | |
static func postItem(item: Item) { | |
itemsReference.childByAutoId().setValue(item.toAnyObject()) | |
} | |
// MARK: GET Methods | |
static func getAllItems(completion: @escaping(_ items: [Item]) -> Void) { | |
itemsReference.observe(.value) { snapshot in | |
var items: [Item] = [] | |
for item in snapshot.children { | |
guard let item = item as? DataSnapshot, | |
let currentItem = Item(snapshot: item) else { | |
continue | |
} | |
items.append(currentItem) | |
} | |
completion(items) | |
} | |
} | |
} |
The last thing we need to do is add the important methods into the MainViewController.
The MainViewController will start with the viewDidLoad method, so inside here, we will call the method loadItems(). Our loadItems function uses our ItemsRemote class to get the list of items from the database. It then sets the items that it retrieves into our array of items, which will then refresh the table view.
The other two methods are going to enable us to post items to the server. All we need to do is create the add IBAction which will be called whenever the add button is pressed. We can then use the text inside the UITextField to create an Item and post it to the database using our ItemsRemote class. The last helper function called textFieldFinished just dismisses the keyboard and sets the text of the UITextField to nil after we have finished posting.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MARK: View Methods | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
loadItems() | |
} | |
// MARK: Action Methods | |
@IBAction func add(_ sender: Any?) { | |
guard let itemText = addTextField.text else { | |
return | |
} | |
textFieldFinished() | |
let item: Item = Item(item: itemText) | |
ItemsRemote.postItem(item: item) | |
} | |
// MARK: Helper Methods | |
private func loadItems() { | |
ItemsRemote.getAllItems { items in | |
self.items = items | |
} | |
} | |
private func textFieldFinished() { | |
addTextField.text = nil | |
addTextField.resignFirstResponder() | |
} |
If you have followed all of the steps correctly and I haven’t missed anything out, your item list should be working.
The source code for the finished dynamic item list is available here:
https://github.com/rtking1993/FirebaseTest/tree/feature/finished