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!

firebase ios

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

What is firebase,firebase bangla tutorial.

LIKE | COMMENT | SHARE | SUBSCRIBE The Firebase Realtime Database is a cloud-hosted NoSQL database that lets you store and sync data between your users in re...

Firebase Introduction with C#

LIKE | COMMENT | SHARE | SUBSCRIBE Firebase is a mobile and web application development platform developed by Firebase, Inc. in 2011, then acquired by Google...

Create database into firebase

LIKE | COMMENT | SHARE | SUBSCRIBE In this video, I will show you how to Create database into #firebase console. Subscribe & Stay Connected! Thank You! ♥ #Fi...

Save Employee into firebase

LIKE | COMMENT | SHARE | SUBSCRIBE In this video, I will show you how to save employee information into firebase database. Subscribe & Stay Connected! Thank ...

Hire iOS App Developer

Are you looking to transform your idea into an iPhone application? Hire iPhone programmer team from **[HourlyDeveloper.io](https://hourlydeveloper.io/ "HourlyDeveloper.io")** to ensure the best results, utilizing all the latest trends in iOS app...