1. Overview
Welcome to the Friendly Chat codelab. In this codelab, you'll learn how to use the Firebase platform to create iOS applications. You will implement a chat client and monitor its performance using Firebase.
What you'll learn
- Allow users to sign in.
- Sync data using the Firebase Realtime Database.
- Store binary files in Firebase Storage.
What you'll need
- Xcode
- CocoaPods
- A test device with iOS 8.0+ or simulator
How will you use this tutorial?
How would rate your experience with building iOS apps?
2. Get the sample code
Clone the GitHub repository from the command line.
$ git clone https://github.com/firebase/codelab-friendlychat-ios
3. Build the starter app
To build the starter app:
- In a terminal window, navigate to the
ios-starter/swift-starter
directory from your sample code download - Run
pod install --repo-update
- Open the FriendlyChatSwift.xcworkspace file to open the project in Xcode.
- Click the Run button.
You should see the Friendly Chat home screen appear after a few seconds. The UI should appear. However, at this point you cannot sign in, send or receive messages. The app will abort with an exception until you complete the next step.
4. Create Firebase console Project
Create project
From Firebase console select Add Project.
Call the project FriendlyChat
, then click on Create Project.
Connect your iOS app
- From the Project Overview screen of your new project, click Add Firebase to your iOS app.
- Enter the bundle ID, as "
com.google.firebase.codelab.FriendlyChatSwift
". - Enter the App Store id as "
123456
". - Click Register App.
Add GoogleService-Info.plist file to your app
On the second screen click Download GoogleService-Info.plist to download a configuration file that contains all the necessary Firebase metadata for your app. Copy that file to your application and add it to the FriendlyChatSwift target.
You can now click the "x" in the upper right corner of the popup to close it – skipping steps 3 and 4 – as you will perform those steps here.
Import Firebase module
Start by making sure the Firebase
module is imported.
AppDelegate.swift, FCViewController.swift
import Firebase
Configure Firebase in AppDelegate
Use the "configure" method in FirebaseApp inside the application:didFinishLaunchingWithOptions function to configure underlying Firebase services from your .plist file.
AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions
launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
GIDSignIn.sharedInstance().delegate = self
return true
}
5. Identify Users
Use Rules To Restrict To Authenticated Users
We will now add a rule to require authentication before reading or writing any messages. To do this we add the following rules to our messages data object. From within the Database section of Firebase console select Realtime Database, then click on the Rules tab. Then update the rules so they look like this:
{
"rules": {
"messages": {
".read": "auth != null",
".write": "auth != null"
}
}
}
For more information on how this works (including documentation on the "auth" variable) see the Firebase security documentation.
Configure Authentication APIs
Before your application can access the Firebase Authentication APIs on behalf of your users, you will have to enable it
- Navigate to the Firebase console and select your project
- Select Authentication
- Select the Sign In Method tab
- Toggle the Google switch to enabled (blue)
- Press Save on the resulting dialog
If you get errors later in this codelab with the message "CONFIGURATION_NOT_FOUND", come back to this step and double check your work.
Confirm Firebase Auth dependency
Confirm Firebase Auth dependencies exist in the Podfile
file.
Podfile
pod 'Firebase/Auth'
Setup your Info.plist for Google Sign In.
You'll need to add a custom URL scheme to your XCode project.
- Open your project configuration: double-click the project name in the left tree view. Select your app from the TARGETS section, then select the Info tab, and expand the URL Types section.
- Click the + button, and add a URL scheme for your reversed client ID. To find this value, open the GoogleService-Info.plist configuration file, and look for the REVERSED_CLIENT_ID key. Copy the value of that key, and paste it into the URL Schemes box on the configuration page. Leave the other fields blank.
- When completed, your config should look something similar to the following (but with your application-specific values):
Set clientID for Google Sign In
After Firebase is configured, we can use the clientID to set up the Google Sign In inside the "didFinishLaunchingWithOptions:" method.
AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions
launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
GIDSignIn.sharedInstance().clientID = FirebaseApp.app()?.options.clientID
GIDSignIn.sharedInstance().delegate = self
return true
}
Add the sign in handler
Once the result of the Google Sign-In was successful, use the account to authenticate with Firebase.
AppDelegate.swift
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error?) {
if let error = error {
print("Error \(error)")
return
}
guard let authentication = user.authentication else { return }
let credential = GoogleAuthProvider.credential(withIDToken: authentication.idToken,
accessToken: authentication.accessToken)
Auth.auth().signIn(with: credential) { (user, error) in
if let error = error {
print("Error \(error)")
return
}
}
}
Automatically sign in the user. Then add a listener to Firebase Auth, to let the user into the app, after successful sign in. And remove the listener on deinit.
SignInViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
GIDSignIn.sharedInstance().uiDelegate = self
GIDSignIn.sharedInstance().signInSilently()
handle = Auth.auth().addStateDidChangeListener() { (auth, user) in
if user != nil {
MeasurementHelper.sendLoginEvent()
self.performSegue(withIdentifier: Constants.Segues.SignInToFp, sender: nil)
}
}
}
deinit {
if let handle = handle {
Auth.auth().removeStateDidChangeListener(handle)
}
}
Sign Out
Add the Sign out method
FCViewController.swift
@IBAction func signOut(_ sender: UIButton) {
let firebaseAuth = Auth.auth()
do {
try firebaseAuth.signOut()
dismiss(animated: true, completion: nil)
} catch let signOutError as NSError {
print ("Error signing out: \(signOutError.localizedDescription)")
}
}
Test Reading Messages as Signed In User
- Click the Run button.
- You should be immediately sent to the sign-in screen. Tap the Google Sign-In button.
- You should then be sent to the messaging screen if everything worked well.
6. Activate Realtime Database
Import Messages
In your project in Firebase console select the Database item on the left navigation bar. In the overflow menu of the Database select Import JSON. Browse to the initial_messages.json
file in the friendlychat directory, select it then click the Import button. This will replace any data currently in your database. You could also edit the database directly, using the green + and red x to add and remove items.
After importing your database should look like this:
Confirm Firebase Database Dependency
In the dependencies block of the Podfile
file, confirm that Firebase/Database
is included.
Podfile
pod 'Firebase/Database'
Synchronize Existing Messages
Add code that synchronizes newly added messages to the app UI.
The code you add in this section will:
- Initialize the Firebase database and add a listener to handle changes made to the database.
- Update the
DataSnapshot
so new messages will be shown.
Modify your FCViewController's "deinit", "configureDatabase", and "tableView:cellForRow indexPath:" methods; replace with the code defined below:
FCViewController.swift
deinit {
if let refHandle = _refHandle {
self.ref.child("messages").removeObserver(withHandle: _refHandle)
}
}
func configureDatabase() {
ref = Database.database().reference()
// Listen for new messages in the Firebase database
_refHandle = self.ref.child("messages").observe(.childAdded, with: { [weak self] (snapshot) -> Void in
guard let strongSelf = self else { return }
strongSelf.messages.append(snapshot)
strongSelf.clientTable.insertRows(at: [IndexPath(row: strongSelf.messages.count-1, section: 0)], with: .automatic)
})
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Dequeue cell
let cell = self.clientTable.dequeueReusableCell(withIdentifier: "tableViewCell", for: indexPath)
// Unpack message from Firebase DataSnapshot
let messageSnapshot = self.messages[indexPath.row]
guard let message = messageSnapshot.value as? [String: String] else { return cell }
let name = message[Constants.MessageFields.name] ?? ""
let text = message[Constants.MessageFields.text] ?? ""
cell.textLabel?.text = name + ": " + text
cell.imageView?.image = UIImage(named: "ic_account_circle")
if let photoURL = message[Constants.MessageFields.photoURL], let URL = URL(string: photoURL),
let data = try? Data(contentsOf: URL) {
cell.imageView?.image = UIImage(data: data)
}
return cell
}
Test Message Sync
- Click the Run button.
- Click the Sign in to get started button to go to the messages window.
- Add new messages directly in Firebase console by clicking on the green + symbol next to the "messages" entry and adding an object like the following:
- Confirm that they show up in the Friendly-Chat UI.
7. Send Messages
Implement Send Message
Push values to the database. When you use the push method to add data to Firebase Realtime Database, an automatic ID will be added. These auto generated IDs are sequential, which ensures that new messages will be added in the correct order.
Modify your FCViewController's "sendMessage:" method; replace with the code defined below:
FCViewController.swift
func sendMessage(withData data: [String: String]) {
var mdata = data
mdata[Constants.MessageFields.name] = Auth.auth().currentUser?.displayName
if let photoURL = Auth.auth().currentUser?.photoURL {
mdata[Constants.MessageFields.photoURL] = photoURL.absoluteString
}
// Push data to Firebase Database
self.ref.child("messages").childByAutoId().setValue(mdata)
}
Test Sending Messages
- Click the Run button.
- Click Sign In to go to the messages window.
- Type out a message and hit send. The new message should be visible in the app UI and in the Firebase console.
8. Store and Receive images
Confirm Firebase Storage Dependency
In the dependencies block of the Podfile
, confirm Firebase/Storage
is included.
Podfile
pod 'Firebase/Storage'
Activate Firebase Storage in dashboard
Go to Firebase console and confirm that Storage is activated with "gs://PROJECTID.appspot.com" domain
If you are seeing the activation window instead, click "GET STARTED" to activate it with default rules.
Configure FirebaseStorage
FCViewController.swift
func configureStorage() {
storageRef = Storage.storage().reference()
}
Receive images in existing messages
Add code that downloads images from Firebase Storage.
Modify your FCViewController's "tableView: cellForRowAt indexPath:" method; replace with the code defined below:
FCViewController.swift
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Dequeue cell
let cell = self.clientTable .dequeueReusableCell(withIdentifier: "tableViewCell", for: indexPath)
// Unpack message from Firebase DataSnapshot
let messageSnapshot: DataSnapshot! = self.messages[indexPath.row]
guard let message = messageSnapshot.value as? [String:String] else { return cell }
let name = message[Constants.MessageFields.name] ?? ""
if let imageURL = message[Constants.MessageFields.imageURL] {
if imageURL.hasPrefix("gs://") {
Storage.storage().reference(forURL: imageURL).getData(maxSize: INT64_MAX) {(data, error) in
if let error = error {
print("Error downloading: \(error)")
return
}
DispatchQueue.main.async {
cell.imageView?.image = UIImage.init(data: data!)
cell.setNeedsLayout()
}
}
} else if let URL = URL(string: imageURL), let data = try? Data(contentsOf: URL) {
cell.imageView?.image = UIImage.init(data: data)
}
cell.textLabel?.text = "sent by: \(name)"
} else {
let text = message[Constants.MessageFields.text] ?? ""
cell.textLabel?.text = name + ": " + text
cell.imageView?.image = UIImage(named: "ic_account_circle")
if let photoURL = message[Constants.MessageFields.photoURL], let URL = URL(string: photoURL),
let data = try? Data(contentsOf: URL) {
cell.imageView?.image = UIImage(data: data)
}
}
return cell
}
9. Send Image Messages
Implement Store and Send Images
Upload an image from the user, then sync this image's storage URL to database so this image is sent inside the message.
Modify your FCViewController's "imagePickerController: didFinishPickingMediaWithInfo:" method; replace with the code defined below:
FCViewController.swift
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String : Any]) {
picker.dismiss(animated: true, completion:nil)
guard let uid = Auth.auth().currentUser?.uid else { return }
// if it's a photo from the library, not an image from the camera
if #available(iOS 8.0, *), let referenceURL = info[UIImagePickerControllerReferenceURL] as? URL {
let assets = PHAsset.fetchAssets(withALAssetURLs: [referenceURL], options: nil)
let asset = assets.firstObject
asset?.requestContentEditingInput(with: nil, completionHandler: { [weak self] (contentEditingInput, info) in
let imageFile = contentEditingInput?.fullSizeImageURL
let filePath = "\(uid)/\(Int(Date.timeIntervalSinceReferenceDate * 1000))/\((referenceURL as AnyObject).lastPathComponent!)"
guard let strongSelf = self else { return }
strongSelf.storageRef.child(filePath)
.putFile(from: imageFile!, metadata: nil) { (metadata, error) in
if let error = error {
let nsError = error as NSError
print("Error uploading: \(nsError.localizedDescription)")
return
}
strongSelf.sendMessage(withData: [Constants.MessageFields.imageURL: strongSelf.storageRef.child((metadata?.path)!).description])
}
})
} else {
guard let image = info[UIImagePickerControllerOriginalImage] as? UIImage else { return }
let imageData = UIImageJPEGRepresentation(image, 0.8)
let imagePath = "\(uid)/\(Int(Date.timeIntervalSinceReferenceDate * 1000)).jpg"
let metadata = StorageMetadata()
metadata.contentType = "image/jpeg"
self.storageRef.child(imagePath)
.putData(imageData!, metadata: metadata) { [weak self] (metadata, error) in
if let error = error {
print("Error uploading: \(error)")
return
}
guard let strongSelf = self else { return }
strongSelf.sendMessage(withData: [Constants.MessageFields.imageURL: strongSelf.storageRef.child((metadata?.path)!).description])
}
}
}
Test Sending and Receiving Image Messages
- Click the Run button.
- Click Sign In to go to the messages window.
- Click "add a photo" icon to pick a photo. The new message with the photo should be visible in the app UI and in the Firebase console.
10. Congratulations!
You have used Firebase to easily build a real-time chat application.
What we've covered
- Realtime Database
- Federated Sign In
- Storage