Creating WhatsApp Clone Using Firebase

Creating WhatsApp Clone Using Firebase

In this tutorial, we will be making use of Firebase to create a WhatsApp clone.

In this tutorial, we will be making use of Firebase to create a WhatsApp clone.

Prerequisites

I will not be going through the entire detail of implementing this app, mainly just the logic and code files. This tutorial assumes that you do have an existing knowledge of working with simple apps for iOS, and we will build on that. But do not fret, I will include the entire source code below for your reference if you with to learn it line by line.

For this tutorial I have used XCode 10 and Swift 4.2.

Tutorial

Let’s first create a new project, FakeChat in any folder you like. You can choose to include or exclude Core Data / Unit / UI Tests, as I will not be covering them here.

Creating New Project with Pods

Create a single view app FakeChat

Next we will be installing various pods that will be used in this tutorial:

pod init
open Podfile -a Xcode

Add the required pods

pod install

Now that we have installed the required dependencies, let’s create a new Firebase Project.

Setting Up Firebase

Head over here and press Go To Console on the top right (make sure you are logged in).

After that, click Add Project with default settings and locations. In production apps, you may want to change the location depending on your needs.

Select Project Overview and add a new iOS App to the Project, Make sure to use your iOS Bunde ID, since it has to be unique for our app to work. Replace com.bilguun.FakeChat with something unique to you such as com.yourorgname.FakeChat

Click on Register app and download the GoogleService-Info.plist. We will add this to the root of our project.

Make sure to add FakeChat as our target

Now the only thing we really need to do is to add the the following in our AppDelegate.swift file didFinishLaunchingWithOption method.

FirebaseApp.configure()
let database = Database.database().reference()
database.setValue("Testing")

Creating Database

Now Firebase currently offers 2 types of databases that support cloud syncing. These are Cloud Firestore and *Realtime Database. *Essentially Cloud Firestore is an upgraded Realtime Database.

Cloud Firestore is Firebase’s new flagship database for mobile app development. It improves on the successes of the Realtime Database with a new, more intuitive data model. Cloud Firestore also features richer, faster queries and scales better than the Realtime Database.
It is easier to scale and can model complex models and is better overall in the long run. However, to keep things simple, we will be sticking to the Realtime Database.

Realtime Database

Next, go back to Firebase Console and do the following:

This will create a *Realtime Database *in testing mode, which means, users do not have to be authenticated to read and write into this database. We will be changing the rules eventually but let’s go ahead with this so we can test our app.

Go ahead and run our iOS app on a simulator. Once it has started up, when u click on Database in Firebase, you should see something like below:

Our set value method worked!

Great! Once our app has finished launching, we have referenced the root of our realtime database, and set a new value Testing

We will now set aside Firebase Database, and come back to it again when we are ready to send messages. Let us now implement our ViewControllers and Signup / Login logic for our users.

Registering and Logging in

Let’s go to Authentication and click on Email / Password and enable that. What we are doing here is that we are giving the ability to our users to signup using email or password. We won’t checking the validity of the emails or authenticity of the users in this tutorial.

Firebase also has a lot more options to allow users to signup / login, feel free to explore that and incorporate that in your app.

Creating ViewControllers

This will be our initial storyboard

Let’s go about and create our initial storyboard. Here we will have just 2 screens embedded in Navigation Controller. Our welcome screen has input fields for email and password. We can then either login or register. Once we have done either one of those, we can present our Chats ViewController

See the screen recording above to get the gist of the current flow.

Handling Registration and Logging in

//
// ViewController.swift
// FakeChat
//
// Created by Bilguun Batbold on 23/3/19.
// Copyright © 2019 Bilguun. All rights reserved.
//
import UIKit
import NotificationCenter
import Firebase
import SVProgressHUD
class ViewController: UIViewController {
@IBOutlet weak var buttonStackView: UIStackView!
@IBOutlet weak var emailTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
@IBAction func signUpOrLoginDidTap(_ sender: UIButton) {
// try and get the required fields
guard let email = emailTextField.text, let password = passwordTextField.text else {
//show alert if not filled
let alert = UIAlertController(title: "Error", message: "Please ensure required fields are filled", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
return
}
//button tags have been set in the storyboard 0 -> register 1 -> Login
switch sender.tag {
case 0:
registerUser(email: email, password: password)
case 1:
loginUser(email: email, password: password)
default:
return
}
}
private func registerUser(email: String, password: String) {
SVProgressHUD.show(withStatus: "Registering..")
//create user and wait for callback
Auth.auth().createUser(withEmail: email, password: password) { (result, error) in
if error != nil {
print(error?.localizedDescription as Any)
}
else {
// if not error, navigate to next page
self.performSegue(withIdentifier: "showChat", sender: self)
}
SVProgressHUD.dismiss()
}
}
private func loginUser(email: String, password: String) {
SVProgressHUD.show(withStatus: "Logging in..")
Auth.auth().signIn(withEmail: email, password: password) { (result, error) in
if error != nil {
print(error?.localizedDescription as Any)
}
else {
self.performSegue(withIdentifier: "showChat", sender: self)
}
SVProgressHUD.dismiss()
}
}
@IBAction func unwindToLogin(_ unwindSegue: UIStoryboardSegue) {
do {
try Auth.auth().signOut()
print("user signed out")
}
catch {
print("Error signing out")
}
emailTextField.text?.removeAll()
passwordTextField.text?.removeAll()
}
}

Update your main ViewController.swift to be like this. Make sure to connect the IBOutlets and IBActions in the storyboard to prevent crashing.

Let’s now run the app and register a new user

Enter whatever email you want and a password. Click register and the app should take you to the ChatsViewController after a brief delay.

Authentication page

In the Firebase, refresh the Authentication page and you should see a new user that we have just registered. What we have is:

  • Identifier — Email we used
  • Providers — Icon showing what type of authentication it is
  • Created — Created date
  • Signed In — Date user last signed in
  • User UUID — Unique identifier assigned to each user
  • Password column not shown since it is sensitive data. It will be hashed and stored accordingly.

Go back to the main page and try logging in. Once the user has successfully logged in, we once again show the ChatsViewController.

ChatsViewController

Looks pretty decent!

This is what we will be implementing in our ChatsViewController. The basic idea is as follows:

  1. Create a custom model that will hold message, incoming, sender
  2. Create custom table view cell to define message alignment and background colour based on the model received. If there sender is not you, show the sender name on top of the message
  3. Display the cells in the table view.
//
// ChatMessageCell.swift
// FakeChat
//
// Created by Bilguun Batbold on 23/3/19.
// Copyright © 2019 Bilguun. All rights reserved.
//
import Foundation
import UIKit
class ChatMessageCell: UITableViewCell {
let messageLabel = UILabel()
let messageBgView = UIView()
// change background view colour accordingly
var isIncoming: Bool = false {
didSet {
messageBgView.backgroundColor = isIncoming ? UIColor.white : #colorLiteral(red: 0.8823529412, green: 0.968627451, blue: 0.7921568627, alpha: 1)
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
addSubview(messageBgView)
addSubview(messageLabel)
messageBgView.translatesAutoresizingMaskIntoConstraints = false
messageBgView.layer.cornerRadius = 7
messageLabel.numberOfLines = 0
messageLabel.translatesAutoresizingMaskIntoConstraints = false
// set constraints for the message and the background view
let constraints = [
messageLabel.topAnchor.constraint(equalTo: topAnchor, constant: 24),
messageLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -24),
messageBgView.topAnchor.constraint(equalTo: messageLabel.topAnchor, constant: -16),
messageBgView.leadingAnchor.constraint(equalTo: messageLabel.leadingAnchor, constant: -16),
messageBgView.bottomAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: 16),
messageBgView.trailingAnchor.constraint(equalTo: messageLabel.trailingAnchor, constant: 16)
]
NSLayoutConstraint.activate(constraints)
selectionStyle = .none
backgroundColor = .clear
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
// what we will call from our tableview method
func configure(with model: MessageModel) {
isIncoming = model.isIncoming
if isIncoming {
guard let sender = model.sender else {return}
// align to the left
let nameAttributes = [
NSAttributedString.Key.foregroundColor : UIColor.orange,
NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 16)
] as [NSAttributedString.Key : Any]
// sender name at top, message at the next line
let senderName = NSMutableAttributedString(string: sender + "\n", attributes: nameAttributes)
let message = NSMutableAttributedString(string: model.message)
senderName.append(message)
messageLabel.attributedText = senderName
messageLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 32).isActive = true
messageLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -32).isActive = false
}
else {
// align to the right
messageLabel.text = model.message
messageLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -32).isActive = true
messageLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 32).isActive = false
}
}
}
// message struct
struct MessageModel {
let message: String
let sender: String?
let isIncoming: Bool
}

Chat Message Cell

//
// ChatsViewController.swift
// FakeChat
//
// Created by Bilguun Batbold on 23/3/19.
// Copyright © 2019 Bilguun. All rights reserved.
//
import UIKit
class ChatsViewController: UIViewController {
//chatcell identifier
private let cellId = "chatCell"
//mock data to display
private let messages = [MessageModel.init(message: "My first message", sender: "User 1", isIncoming: true), MessageModel.init(message: "Somewhat maybe a long message about how my day was", sender: "User 1", isIncoming: true), MessageModel.init(message: "Very lengthy message on what exactly happened to me the whole day and how I have spent my weekend off just doing some coding and writing tutorials", sender: nil, isIncoming: false)]
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var textFieldViewHeight: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
func setup() {
//set the delegates
tableView.delegate = self
tableView.dataSource = self
tableView.register(ChatMessageCell.self, forCellReuseIdentifier: cellId)
// do not show separators and set the background to gray-ish
tableView.separatorStyle = .none
tableView.backgroundColor = UIColor(white: 0.95, alpha: 1)
// extension of this can be found in the ViewController.swift
// basically hides the keyboard when tapping anywhere
hideKeyboardOnTap()
}
}
extension ChatsViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! ChatMessageCell
cell.configure(with: messages[indexPath.row])
return cell
}
}
extension ChatsViewController: UITextFieldDelegate {
//handle when keyboard is shown and hidden
func textFieldDidBeginEditing(_ textField: UITextField) {
UIView.animate(withDuration: 0.3) {
self.textFieldViewHeight.constant = 308
self.view.layoutIfNeeded()
}
}
func textFieldDidEndEditing(_ textField: UITextField) {
UIView.animate(withDuration: 0.3) {
self.textFieldViewHeight.constant = 50
self.view.layoutIfNeeded()
}
}
}

Chats View Controller

This is all we really need to display the messages accordingly. Let’s now connect our TableView data to Firebase! We will send a message and ensure that we can get it back on another simulator.

Connecting to the Firebase database

We are going to add 2 new methods to be able to communicate with Firebase Database:

  1. Create a custom model that will hold message, incoming, sender
  2. Create custom table view cell to define message alignment and background colour based on the model received. If there sender is not you, show the sender name on top of the message
  3. Display the cells in the table view.

I will not bore you with too many words, so here is the actual implementation

//
// ChatsViewController.swift
// FakeChat
//
// Created by Bilguun Batbold on 23/3/19.
// Copyright © 2019 Bilguun. All rights reserved.
//
import UIKit
import Firebase
class ChatsViewController: UIViewController {
//chatcell identifier
private let cellId = "chatCell"
private var messages = [MessageModel]()
let messageDB = Database.database().reference().child("Messages")
//MARK: Outlets
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var textFieldViewHeight: NSLayoutConstraint!
@IBOutlet weak var messageTextField: UITextField!
@IBOutlet weak var sendButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
messageDB.removeAllObservers()
}
func setup() {
//set the delegates
tableView.delegate = self
tableView.dataSource = self
tableView.register(ChatMessageCell.self, forCellReuseIdentifier: cellId)
// do not show separators and set the background to gray-ish
tableView.separatorStyle = .none
tableView.backgroundColor = UIColor(white: 0.95, alpha: 1)
getMessages()
// extension of this can be found in the ViewController.swift
// basically hides the keyboard when tapping anywhere
hideKeyboardOnTap()
}
// call this to listen to database changes and add it into our tableview
func getMessages() {
messageDB.observe(.childAdded) { (snapshot) in
let snapshotValue = snapshot.value as! Dictionary<String, String>
guard let message = snapshotValue["message"], let sender = snapshotValue["sender"] else {return}
let isIncoming = (sender == Auth.auth().currentUser?.email ? false : true)
let chatMessage = MessageModel.init(message: message, sender: sender, isIncoming: isIncoming)
self.addNewRow(with: chatMessage)
}
}
// function to add our cells with animation
func addNewRow(with chatMessage: MessageModel) {
self.tableView.beginUpdates()
self.messages.append(chatMessage)
let indexPath = IndexPath(row: self.messages.count-1, section: 0)
self.tableView.insertRows(at: [indexPath], with: .top)
self.tableView.endUpdates()
}
//MARK: Buttons
@IBAction func sendButtonDidTap(_ sender: Any) {
// return if message does not exist
guard let message = messageTextField.text else {return}
if message == "" {
return
}
//stop editing the message
messageTextField.endEditing(true)
// disable the buttons to avoid complication for simplicity
messageTextField.isEnabled = false
sendButton.isEnabled = false
let messageDict = ["sender": Auth.auth().currentUser?.email, "message" : message]
messageDB.childByAutoId().setValue(messageDict) { (error, reference) in
if error != nil {
print(error?.localizedDescription as Any)
}
else {
print("Message sent!")
//enable the buttons and remove the text
self.messageTextField.isEnabled = true
self.sendButton.isEnabled = true
self.messageTextField.text?.removeAll()
}
}
}
}
// MARK: - TableView Delegates
extension ChatsViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! ChatMessageCell
cell.configure(with: messages[indexPath.row])
return cell
}
}
//MARK: - TextField Delegates
extension ChatsViewController: UITextFieldDelegate {
//handle when keyboard is shown and hidden
func textFieldDidBeginEditing(_ textField: UITextField) {
UIView.animate(withDuration: 0.3) {
self.textFieldViewHeight.constant = 308
self.view.layoutIfNeeded()
}
}
func textFieldDidEndEditing(_ textField: UITextField) {
UIView.animate(withDuration: 0.3) {
self.textFieldViewHeight.constant = 50
self.view.layoutIfNeeded()
}
}
}
extension ChatsViewController {
func hideKeyboardOnTap() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard(_:)))
tap.cancelsTouchesInView = false
tableView.addGestureRecognizer(tap)
}
@objc func dismissKeyboard(_ sender: UITapGestureRecognizer) {
view.endEditing(true)
if let navController = self.navigationController {
navController.view.endEditing(true)
}
}
}

Go through the code and try to understand what exactly happened. The new methods are sendButtonDidTap and getMessages. That is all required to properly communicate with our Firebase Database. Go ahead and run the app, register 2 users if you have not done so, and login with them on a simulator or your phone. The end result should be something like this:

The messages are sent instantly, and received instantly as well.

Concluding Thoughts

About our app

Yes I know, although our WhatsApp clone kind of works, it does not have any idea / concept of friends. Which means at this stage, the ChatsViewController acts like 1 huge group where all your registered members can send and receive messages. In order to incorporate the idea of sending messages to friends / groups / rooms, our Database structure will need to be changed to facilitate that. Perhaps I will give an update on how you can achieve that using Firebase in the near future. If any one does want to know how that can be done, feel free to let me know as well.

Firebase can be a really powerful tool to get started with real time information exchange if you do not have the necessary skills or resources to get your own server. In the future I will update this or create a new tutorial that covers implementing our own service using Sockets / MongoDB instead of Firebase. But to get started, Firebase provides a super neat way of allowing real time information sharing.

The final source code can be found here.

If anyone finds these useful, feel free to share this or let me know should there be an error / bad practice / implementations.

Have fun coding!

How to integrate your iOS Flutter App with Firebase on MacOS

How to integrate your iOS Flutter App with Firebase on MacOS

In this tutorial I am going to show you how to connect your Flutter iOS application to the Firebase Platform on a Mac Computer so you can utilize the powerful services provided by the firebase API in your future endeavors…

In this tutorial I am going to show you how to connect your Flutter iOS application to the Firebase Platform on a Mac Computer so you can utilize the powerful services provided by the firebase API in your future endeavors…

Firebase is a mobile app development platform developed by Firebase, Inc. in 2011, and then Acquired by Google in 2014. It provides various features such as Cloud Storage, Authentication and an ML kit, which are essential to developing modern mobile applications. Additionally, it provides services such as Performance Monitoring, Crashlytics and Google Analytics to help you improve the quality of your applications.

1. Readying a Gmail Account and a Flutter Project

In order to utilize services from Firebase and Google Cloud Platform, you will need a Google Account. If you do not have one, simply following the page instructions here to register for one.

This tutorial is going to show you how to connect your existing Flutter application to the Firebase platform. If you are interested in how to create your first Flutter application, I have a tutorial on How to create your first iOS Flutter app on MacOS. By the end of that tutorial, you should have a hello_world application ready in the simulator and understand how to alter the application by modifying the main.dart file.

2. Creating a Firebase Project

In order to integrate your Flutter application with the Firebase Platform, first you have to create a Firebase Project. And here are the steps.

  1. Go to the Firebase Console.

  2. Click on the big Add project button.

  1. Enter your Project name.
  • I used hello-world for this example. Firebase automatically appends a unique ID to your project name — for example, the project I created ended up with the name hello-world-f2206.
  1. You can pick a Cloud Firestore Location.
  • I used hello-world for this example. Firebase automatically appends a unique ID to your project name — for example, the project I created ended up with the name hello-world-f2206.
  1. Accept the Terms and Conditions.

  1. Once you are done, scroll to the bottom and click Create Project.

  • I used hello-world for this example. Firebase automatically appends a unique ID to your project name — for example, the project I created ended up with the name hello-world-f2206.

Firebase will take some time to ready your application. Once done, click on the Continue button to open up the Firebase Project Overview Page.

4. Configure an iOS Application

  1. In your Firebase Project Overview Page, launch the setup wizard for iOS.

  1. Inside the setup wizard, put in the iOS bundle ID. The Register app button should then light up, click on it.
  • I used hello-world for this example. Firebase automatically appends a unique ID to your project name — for example, the project I created ended up with the name hello-world-f2206.

  1. Download the GoogleService-Info.plist configuration file and put it into the iOS Project root folder, then click Next.
  • I used hello-world for this example. Firebase automatically appends a unique ID to your project name — for example, the project I created ended up with the name hello-world-f2206.

  1. Follow the instructions to add the Firebase SDK, then click Next.

  • I used hello-world for this example. Firebase automatically appends a unique ID to your project name — for example, the project I created ended up with the name hello-world-f2206.
  1. Modify the code inside the main AppDelegate as instructed by the setup wizard then click Next. For this example, I used Objective-C, and therefore replaced the contents inside AppDelegate.m with the following code.
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"

@import UIKit;
@import Firebase;

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[FIRApp configure];
return YES;
}

@end

  1. Get back to the root folder and run your app, after a while you should see the setup wizard showing that your app is added to Firebase. Click Continue to the Console to finish the setup.

Congratulations! You have successful added Firebase to your Flutter application. Despite the fact that having both Firebase and Flutter from Google is supper cool, it is actually a good Software Engineering practice to always have a plan B, as well as plan C, D,E, F, and G. In the future I will write another guide on an example application utilizing Firebase, and more on Flutter.

Have fun coding!!!

Appendices:

3.1 Switching to Administrator Account

If you ran into the following message, it means that you need to contact the organization of your Gmail account to grant you access to Google Developers Console.

4.1 Finding iOS Project root folder & Acquiring Bundle ID

  1. Launch Xcode from the Launchpad.

  2. Select “Open another project…” at the bottom of the prompt screen.

  1. Navigate to your Flutter project folder, open the “ios” folder and select “Runner.xcodeproj”. This should automatically open up the project in Xcode.

  1. Select the Runner project on the left, you should now see the Bundle Identifier under Identity.

4.2 Installing CocoaPods and Firebase SDK

In case the instructions inside the setup wizard did not work out, you will have to remove the existing Podfile in order to reinstall them correctly.

  1. CocoaPods is built with Ruby and is installable with the default Ruby available on MacOS. Use the following commands to install it.
sudo gem install cocoapods

  1. Initialize the Podfile with the following command.
pod init

  1. Then, add the following code to the initialized Podfile.
pod 'Firebase/Core'

  1. Once done, save the changes made to the Podfile, and install the Firebase SDK with the following command.
pod install

  1. After the installation, you will likely have to configure the .xcconfig files. First you will have to copy the files from the Pods/Target Support Files/Pods-Runner folder to Flutter folder.

  1. Then you will have to include them into the Debug.xcconfig and Release.xcconfig files inside the Flutter folder.

In Debug.xcconfig:

#include "Pods-Runner.debug.xcconfig"

In Release.xcconfig:

#include "Pods-Runner.profile.xcconfig"
#include "Pods-Runner.release.xcconfig"

Firebase logout user all sessions

I am using Firebase authentication in my iOS app. Is there any way in Firebase when user login my app with Firebase then logout that user all other devices(sessions)?

I am using Firebase authentication in my iOS app. Is there any way in Firebase when user login my app with Firebase then logout that user all other devices(sessions)?