📜 ⬆️ ⬇️

Introducing Firebase: writing a simple social application on Swift

Due to the unexpected decision of Facebook to close Parse, a lot of developers wondered what to use instead. Today it is almost impossible to imagine a completely autonomous application that would be useful to everyone. In this regard, iOS developers in their work use the tools and resources provided by Apple to access data. Backend-as-a-service, or abbreviated as BaaS is a terrific tool for developers.

Among the best and most popular BaaSs are Firebase from Google. Essentially, Firebase is certainly awesome in performance, implementation and operation. Firebase serves as a database that changes in real time and stores data in JSON. Any changes in the database are immediately synchronized between all clients, or devices that use the same database. In other words, an update in Firebase happens instantly.

Together with the repository, Firebase also provides user authentication, and therefore all data is transmitted over a secure SSL connection. We can choose any combination of email and password for authentication, be it Facebook, Twitter, GitHub, Google, or something else.
')
In addition to the iOS SDK, Firebase has an Android SDK and JavaScript. All platforms can use the same database.
It’s hard to imagine that Firebase with all these features is a budget solution.

At the time of this writing, the Firebase free package can handle up to 100 simultaneous connections. This is quite enough for the work of a popular application. For $ 49 per month there are no restrictions on network activity.

FirebaseJokes application


Today we will create an application with which the user can post various jokes using Firebase. In the application, you can create your account using email and password. It will also be possible to post jokes, the displayed list of jokes will be instantly updated. When another user posts a new joke, the list with jokes will also be updated. Even in the application, we will add a voting function and the funniest jokes will receive the points they deserve.

image

Here is a list of functions that we implement in the FirebaseJokes application:

Let's see the starting project .

First, open the Main.Storyboard to get a visual representation of the application.

image

During the development of the application, we will in practice test the functionality of Firebase and the fact that it is very simple to use. We will have a good time creating FirebaseJokes.

Time to get to know Firebase


Go to the Firebase page and register an account in Firebase, or go to an existing one. We can also register using a GOOGLE account. After registration, we can get 5 minute training, but it is intended for applications written in JavaScript.

image

To see what's in Firebase, click the Manage App in the My First App. This new medium is known as Firebase Forge. This is a cool debugger, and therefore it is worth going through it. The Forge tutorial will help you in creating keys, values, and even child nodes using the plus symbol. It makes sense to look at JSON, right? To exit Forge's short tutorial, click on the toolbar in the upper left corner of the screen.

Creating a new application


image

Time to create FirebaseJokes. On the left of the My First App, click on the transparent rectangle to create a new application. In the APP NAME field, enter “Jokes”, in the APP URL, enter “jokes-your-name”, where “your-name” is your own name. The field must be unique, because This is the url for your application. Click CREATE NEW APP and after the Manage App.

And here is our own Forge screen. When adding data here they are automatically updated in our application. We can also add data for the application in the Forge tab. To understand how our application works, in terms of data, we will enter some data manually.

  1. Click “+” in the jokes-your-name row.
  2. Enter “jokes” in the name field.
  3. Press "+" in a row with a new joke.
  4. Enter a random value in the name field.
  5. Click the "+" in a row with a random value.
  6. Enter “jokeText” in the name field.
  7. Enter “what did one computer say to the other? 11001001010101 ”in the value field.


image

Here is an example of what the jokes object looks like. We will add new jokes to the “jokes” object. We also need the “users” object. Observing how the data created by the application is changing in Forge is interesting and at the same time it is a great practice.

I want to note that all data in the Firebase database is stored as JSON. Unlike Parse, there are no tables and records. When we add data to the Firebase database, it becomes the key of the JSON structure. For example, the data you just created looks like this:

{ "jokes" : { "e32e223r44" : { "jokeText" : "What did one computer say to the other? 11001001010101" } } } 

Now that you have a basic knowledge of the data in the Firebas database, it's time to move on.
But before we proceed to user authentication, we will delete the created data, just as we will do it programmatically from the application.

For FirebaseJokes, we use Email authentication and password. To enable this feature, click Login & Auth on the left pane in Forge. In Email & Password, tick Enable Email & Password Authentication. Immediately under the pressed cell is information about password recovery. Also, pay attention to other authentication options.

image

Add Firebase SDK


To get the base url for the application, return to the Forge main screen. The current url is the url for the application, so copy and paste it into BASE_URL in Constants.swift Xcode.

 import Foundation let BASE_URL = "https://jokes-matt-maher.firebaseio.com" 

It's time to add the Firebase SDK to the application. Before this you need to install CocoaPods. If you have not installed it yet, you can find instructions for installing CocoaPods.

When CocoaPods is installed, open a terminal. Run the following commands to establish Cocoapods in the Xcode project:

 cd <your-xcode-project-directory> pod init 

Then enter the following command to open the Podfile in Xcode:
 open -a Xcode Podfile 

Edit it:

 platform :ios, '8.0' use_frameworks! pod 'Firebase', '>= 2.5.0' 

Then run the following command to download the Firebase SDK:

 pod install 

Open the created file FirebaseJokes.xcworkspace.

To import Firebase SDK, create a new Objective-c File, FileType - Empty File. Call it Temp. When creating a file, the FirebaseJokes-Bridging-Header.h file is automatically created. Write the following line in it:

 #import <Firebase/Firebase.h> 

We delete Temp.m, we will not need it.

Using Firebase SDK


To simplify your life a bit, first we’ll do a little tweaking in DataService.swift. First of all, we need several links:

 import Foundation import Firebase class DataService { static let dataService = DataService() private var _BASE_REF = Firebase(url: "\(BASE_URL)") private var _USER_REF = Firebase(url: "\(BASE_URL)/users") private var _JOKE_REF = Firebase(url: "\(BASE_URL)/jokes") var BASE_REF: Firebase { return _BASE_REF } var USER_REF: Firebase { return _USER_REF } var CURRENT_USER_REF: Firebase { let userID = NSUserDefaults.standardUserDefaults().valueForKey("uid") as! String let currentUser = Firebase(url: "\(BASE_REF)").childByAppendingPath("users").childByAppendingPath(userID) return currentUser! } var JOKE_REF: Firebase { return _JOKE_REF } } 

To use Firebase, you need to import the Firebase firewall. The DataService class serves to interact with Firebase. To read or write data, you must create a link to the Firebase database with a Firebase URL. Base URL is the URL of the application database. Later we will save all users and all jokes in the form of child nodes. To have access to the child nodes, you can simply set the child name (that is, users) to the main URL.

Create a new user account


We'll start with CreateAccountViewController.swift. We need to import our framework.

 import UIKit import Firebase class CreateAccountViewController: UIViewController { 

In the createAccount () method, we take the text that the user entered and try to use it to create a new user. This applies to the Firebase createUser () method. Update the existing method as follows:

 @IBAction func createAccount(sender: AnyObject) { let username = usernameField.text let email = emailField.text let password = passwordField.text if username != "" && email != "" && password != "" { // Set Email and Password for the New User. DataService.dataService.BASE_REF.createUser(email, password: password, withValueCompletionBlock: { error, result in if error != nil { // There was a problem. self.signupErrorAlert("Oops!", message: "Having some trouble creating your account. Try again.") } else { // Create and Login the New User with authUser DataService.dataService.BASE_REF.authUser(email, password: password, withCompletionBlock: { err, authData in let user = ["provider": authData.provider!, "email": email!, "username": username!] // Seal the deal in DataService.swift. DataService.dataService.createNewAccount(authData.uid, user: user) }) // Store the uid for future access - handy! NSUserDefaults.standardUserDefaults().setValue(result ["uid"], forKey: "uid") // Enter the app. self.performSegueWithIdentifier("NewUserLoggedIn", sender: nil) } }) } else { signupErrorAlert("Oops!", message: "Don't forget to enter your email, password, and a username.") } } 

If the entered information about the new user is correct, the user will be registered and will be able to log into the application. For any missing information a notification will pop up. To do this, insert the following method into the class:

 func signupErrorAlert(title: String, message: String) { // Called upon signup error to let the user know signup didn't work. let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert) let action = UIAlertAction(title: "Ok", style: .Default, handler: nil) alert.addAction(action) presentViewController(alert, animated: true, completion: nil) } 

In fact, saving occurs in the createNewAccount () method in DataService.swift.

 func createNewAccount(uid: String, user: Dictionary<String, String>) { // A User is born. USER_REF.childByAppendingPath(uid).setValue(user) } 

To save data to the Firebase database, you can simply call the setValue method. In the code above, the user object is saved to the database under the users key above this child node uid (approx. / Users / 1283834 /).

In addition to saving the user in the Firebase database, we will save the uid for the user in NSUserDefaults. This will track the current user.

User Authorization


Before moving on, import Firebase into LoginViewController.swift. This way we can track if someone has already logged in or is trying to register a user.
In the viewDidAppear () method, we check whether our saved “uid” is nil and whether the user has an account. If the user has been logged in, he skips the login screen. Otherwise, he will be forced to log in.

 override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) // If we have the uid stored, the user is already logger in - no need to sign in again! if NSUserDefaults.standardUserDefaults().valueForKey("uid") != nil && DataService.dataService.CURRENT_USER_REF.authData != nil { self.performSegueWithIdentifier("CurrentlyLoggedIn", sender: nil) } } 

The tryLogin () method, which is called when trying to authorize. Update this method as shown below and insert the loginErrorAlert helper method:

 @IBAction func tryLogin(sender: AnyObject) { let email = emailField.text let password = passwordField.text if email != "" && password != "" { // Login with the Firebase's authUser method DataService.dataService.BASE_REF.authUser(email, password: password, withCompletionBlock: { error, authData in if error != nil { print(error) self.loginErrorAlert("Oops!", message: "Check your username and password.") } else { // Be sure the correct uid is stored. NSUserDefaults.standardUserDefaults().setValue(authData.uid, forKey: "uid") // Enter the app! self.performSegueWithIdentifier("CurrentlyLoggedIn", sender: nil) } }) } else { // There was a problem loginErrorAlert("Oops!", message: "Don't forget to enter your email and password.") } } func loginErrorAlert(title: String, message: String) { // Called upon login error to let the user know login didn't work. let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert) let action = UIAlertAction(title: "Ok", style: .Default, handler: nil) alert.addAction(action) presentViewController(alert, animated: true, completion: nil) } 

Firebase has built-in support for user authentication with an email address and password. Our tryLogin () method uses the authUser () method, which allows you to see if the email and password of the user account match. If so, we save the “uid” and get inside the application. If not, we notify the user that he would repeat the authorization attempt.

Now the application is ready for user registration and authorization, it’s time to break into the application implementation itself.

Joke object


Go to Joke.swift and import Firebase. In our database, the joke is represented by the ID number. This number is generated automatically and contains all the properties of the joke. Here they are:

We initialize a new object in the init () method, where the prank id is the key, the prank data is transferred in the dictionary format.

 class Joke { private var _jokeRef: Firebase! private var _jokeKey: String! private var _jokeText: String! private var _jokeVotes: Int! private var _username: String! var jokeKey: String { return _jokeKey } var jokeText: String { return _jokeText } var jokeVotes: Int { return _jokeVotes } var username: String { return _username } // Initialize the new Joke init(key: String, dictionary: Dictionary<String, AnyObject>) { self._jokeKey = key // Within the Joke, or Key, the following properties are children if let votes = dictionary["votes"] as? Int { self._jokeVotes = votes } if let joke = dictionary["jokeText"] as? String { self._jokeText = joke } if let user = dictionary["author"] as? String { self._username = user } else { self._username = "" } // The above properties are assigned to their key. self._jokeRef = DataService.dataService.JOKE_REF.childByAppendingPath(self._jokeKey) } } 


Adding new jokes


AddJokeViewController.swift import Firebase. The user adds a joke, and we send it to our storage, from where it instantly goes to all devices.

In the viewDidLoad () method, we get the current user username, so we can specify the author of the new joke.

 override func viewDidLoad() { super.viewDidLoad() // Get username of the current user, and set it to currentUsername, so we can add it to the Joke. DataService.dataService.CURRENT_USER_REF.observeEventType(FEventType.Value, withBlock: { snapshot in let currentUser = snapshot.value.objectForKey("username") as! String print("Username: \(currentUser)") self.currentUsername = currentUser }, withCancelBlock: { error in print(error.description) }) } 

When you call the saveJoke () method, a newJoke dictionary is created, which takes text from jokeField, and sets the value to 0 for votes, the current number of votes, and for the current username - author. These values ​​assign them to the appropriate ids and are passed to the createNewJoke () method in the DataService for storage.
Register the parameter to be changed in the AddJokeViewController class:

 var currentUsername = "" 

Update the saveJoke method:

 @IBAction func saveJoke(sender: AnyObject) { let jokeText = jokeField.text if jokeText != "" { // Build the new Joke. // AnyObject is needed because of the votes of type Int. let newJoke: Dictionary<String, AnyObject> = [ "jokeText": jokeText!, "votes": 0, "author": currentUsername ] // Send it over to DataService to seal the deal. DataService.dataService.createNewJoke(newJoke) if let navController = self.navigationController { navController.popViewControllerAnimated(true) } } } 

We use Dictionary to temporarily store jokes. In fact, saving occurs in the createNewJoke method in the DataService. In DataService.swift, add the createNewJoke method:

 func createNewJoke(joke: Dictionary<String, AnyObject>) { // Save the Joke // JOKE_REF is the parent of the new Joke: "jokes". // childByAutoId() saves the joke and gives it its own ID. let firebaseNewJoke = JOKE_REF.childByAutoId() // setValue() saves to Firebase. firebaseNewJoke.setValue(joke) } 

Again, you can save an object using the setValue () method. When the childByAutoId method is called, Firebase generates a unique ID for each joke based on a marker that ensures that the joke receives a unique ID.

Logout current user


Usually this is fixed in the Settings or Profile section, but we will give the user the opportunity to go to AddJokeViewController.swift.

The logout () method uses the firebase unauth () method to deauthorize. It is also necessary to remove the user “uid” from our repository and send it back to LoginViewController.

Update the logout method:

 @IBAction func logout(sender: AnyObject) { // unauth() is the logout method for the current user. DataService.dataService.CURRENT_USER_REF.unauth() // Remove the user's uid from storage. NSUserDefaults.standardUserDefaults().setValue(nil, forKey: "uid") // Head back to Login! let loginViewController = self.storyboard!.instantiateViewControllerWithIdentifier("Login") UIApplication.sharedApplication().keyWindow?.rootViewController = loginViewController } 

If you do not delete the user “uid”, you will have problems entering the new user’s application.

Display all the jokes on screen


Ultimately, the data is obtained with Firebase. We list all the jokes in the UITableView located in JokesFeedTableViewController.swift. Not surprisingly, here we will import Firebase.

Let's start with the viewDidLoad () method. Set our observeEventType () method. Firebase data comes by adding an asynchronous listener to the database link. This method is not called in viewDidLoad () when switching to JokesFeedTableViewController.swift, it is called for any changes to jokes from the database.

 var jokes = [Joke]() override func viewDidLoad() { super.viewDidLoad() // observeEventType is called whenever anything changes in the Firebase - new Jokes or Votes. // It's also called here in viewDidLoad(). // It's always listening. DataService.dataService.JOKE_REF.observeEventType(.Value, withBlock: { snapshot in // The snapshot is a current look at our jokes data. print(snapshot.value) self.jokes = [] if let snapshots = snapshot.children.allObjects as? [FDataSnapshot] { for snap in snapshots { // Make our jokes array for the tableView. if let postDictionary = snap.value as? Dictionary<String, AnyObject> { let key = snap.key let joke = Joke(key: key, dictionary: postDictionary) // Items are returned chronologically, but it's more fun with the newest jokes first. self.jokes.insert(joke, atIndex: 0) } } } // Be sure that the tableView updates when there is new data. self.tableView.reloadData() }) } 

The method provides a snapshot. Using a snapshot, we can create a series of jokes to fill our tableView. For Firebase jokes, we will create a list where the new jokes will be displayed at the top. Because Firebase will distribute the jokes in chronological order, we can only create a row in the opposite direction.

It would be nice to have a constantly updated array of jokes, we remember the need to update the data in the tableView so that we can see it.

The rest of the work is distributed between the tableView: cellForRowAtIndexPath: and our custom cell, JokeCellTableViewCell.swift. In the tableView: cellForRowAtIndexPath: method, we send a joke to the configureCell () method in JokeCellTableViewCell.swift.

 override func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 } override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return jokes.count } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let joke = jokes[indexPath.row] // We are using a custom cell. if let cell = tableView.dequeueReusableCellWithIdentifier("JokeCellTableViewCell") as? JokeCellTableViewCell { // Send the single joke to configureCell() in JokeCellTableViewCell. cell.configureCell(joke) return cell } else { return JokeCellTableViewCell() } } configureCell(), in JokeCellTableViewCell.swift, is where we set the labels and listen for a vote tap. func configureCell(joke: Joke) { self.joke = joke // Set the labels and textView. self.jokeText.text = joke.jokeText self.totalVotesLabel.text = "Total Votes: \(joke.jokeVotes)" self.usernameLabel.text = joke.username // Set "votes" as a child of the current user in Firebase and save the joke's key in votes as a boolean. voteRef = DataService.dataService.CURRENT_USER_REF.childByAppendingPath("votes").childByAppendingPath(joke.jokeKey) // observeSingleEventOfType() listens for the thumb to be tapped, by any user, on any device. voteRef.observeSingleEventOfType(.Value, withBlock: { snapshot in // Set the thumb image. if let thumbsUpDown = snapshot.value as? NSNull { // Current user hasn't voted for the joke... yet. print(thumbsUpDown) self.thumbVoteImage.image = UIImage(named: "thumb-down") } else { // Current user voted for the joke! self.thumbVoteImage.image = UIImage(named: "thumb-up") } }) } func configureCell(joke: Joke) { self.joke = joke // Set the labels and textView. self.jokeText.text = joke.jokeText self.totalVotesLabel.text = "Total Votes: \(joke.jokeVotes)" self.usernameLabel.text = joke.username // Set "votes" as a child of the current user in Firebase and save the joke's key in votes as a boolean. voteRef = DataService.dataService.CURRENT_USER_REF.childByAppendingPath("votes").childByAppendingPath(joke.jokeKey) // observeSingleEventOfType() listens for the thumb to be tapped, by any user, on any device. voteRef.observeSingleEventOfType(.Value, withBlock: { snapshot in // Set the thumb image. if let thumbsUpDown = snapshot.value as? NSNull { // Current user hasn't voted for the joke... yet. print(thumbsUpDown) self.thumbVoteImage.image = UIImage(named: "thumb-down") } else { // Current user voted for the joke! self.thumbVoteImage.image = UIImage(named: "thumb-up") } }) } 

Add the configureCell () method to JokeCellTableViewCell.swift.

 var joke: Joke! var voteRef: Firebase! override func awakeFromNib() { super.awakeFromNib() // UITapGestureRecognizer is set programatically. let tap = UITapGestureRecognizer(target: self, action: "voteTapped:") tap.numberOfTapsRequired = 1 thumbVoteImage.addGestureRecognizer(tap) thumbVoteImage.userInteractionEnabled = true } 

In the voteTapped () method, the listener waits for a signal. In this method, the current user’s “voices” are saved with a key containing the joke id and a true value. All this is sent via the generated voteRef to the configureCell () method.

 func voteTapped(sender: UITapGestureRecognizer) { // observeSingleEventOfType listens for a tap by the current user. voteRef.observeSingleEventOfType(.Value, withBlock: { snapshot in if let thumbsUpDown = snapshot.value as? NSNull { print(thumbsUpDown) self.thumbVoteImage.image = UIImage(named: "thumb-down") // addSubtractVote(), in Joke.swift, handles the vote. self.joke.addSubtractVote(true) // setValue saves the vote as true for the current user. // voteRef is a reference to the user's "votes" path. self.voteRef.setValue(true) } else { self.thumbVoteImage.image = UIImage(named: "thumb-up") self.joke.addSubtractVote(false) self.voteRef.removeValue() } }) } 

The voteTapped () method also relays the signal as a Boolean value to the addSubtractVote () method in Joke.swift. A value of true means the user has voted for the joke; while false means the user has not voted for it yet.

 // Add or Subtract a Vote from the Joke. func addSubtractVote(addVote: Bool) { if addVote { _jokeVotes = _jokeVotes + 1 } else { _jokeVotes = _jokeVotes - 1 } // Save the new vote total. _jokeRef.childByAppendingPath("votes").setValue(_jokeVotes) } 

The addSubtractVote () method in Joke.swift uses a boolean value to add or subtract a vote from joke. Then, the Firebase setValue () method updates the voices against the database.

Application Testing


Now let's test the application. Create a new user and add a few jokes. You will have the opportunity to vote for jokes. And if you look in the toolbar, you will see the created users and the created jokes.

image

Summarize


We did it! This is a pretty funny little application that users will like due to its speed of response. We also gained experience with Firebase.

You can download the finished project finished FirebaseJokes project on GitHub.

For iOS developers, there is a whole world of new features with Firebase. Working with FirebaseJokes will be a good practice, but this is just the beginning.

See other user authentication options, add new features to FirebaseJokes, learn the chat functionality, in short, the possibilities are full.

A couple of tips on storing images: Firebase provides a relatively modest amount of memory to store them, so it’s better to store images in a different place.

Good luck working with Firebase in your future projects!

Source: https://habr.com/ru/post/277941/


All Articles