Categories:
Loading... Search articles

Search for articles

Sorry, but we couldn't find any matches...

But perhaps we can interest you in one of our more popular articles?
Follow this SwiftUI step-by-step tutorial for setting up and implementing Google Sign-In using Firebase authentication.

Google Sign-In & Firebase Authentication Using SwiftUI

May 17, 2021

This article will show you how to set up an iOS app created using SwiftUI. We will implement the Google Sign-In SDK and integrate it with Firebase authentication. Additionally, we will add the Codemagic CI/CD service to successfully build our app. This article is written by Rudrank Riyam.

Apple’s declarative framework SwiftUI is a milestone in Apple platforms development. This framework made mobile development fun for me and many others.

Google provides an SDK to add their Sign-In mechanism to your app. Firebase provides many backend services, one of which is authentication. This helps to authenticate with many social media platforms, email and phone numbers.

We’ll learn how to:

Note: This tutorial assumes you know the basics of SwiftUI.

Introduction

We will work on the authentication screen of Ellifit, an elliptical workout-tracking app. It consists of two screens.

The login screen tells the user about the product and performs a simple onboarding function. The user can sign in to the app through their Google account.

Login screen

After the user signs in, the home screen shows the user’s image, name and email address. The user has the option to sign out of their account.

Login screen

We’ll use SwiftUI to build this app. So let’s get started!

Creating a SwiftUI lifecycle project

Open Xcode, select Create a new Xcode project and select the App template under the iOS header.

Fill in the template options as follows, then click Next:

  • Product Name: Ellifit.
  • Organization Name: Fill this in however you like.
  • Organization Identifier: The identifier you use for your apps.
  • Interface: SwiftUI.
  • Life Cycle: SwiftUI App.
  • Language: Swift.
  • Make sure you’ve unchecked the Use Core Data, Include Unit Tests and UI Tests options.

Choose a directory to save our project, and click Create.

Now that we have our project ready, we need to add a few dependencies to work with Firebase and Google Sign-In.

Adding Firebase and Google Sign-In dependencies

Next, we open Terminal and change the directory to our project. Write the following command to initialize an empty pod file.

pod init

Our project folder has a Podfile inside it. Open the file, and update its content as follows:

target 'Ellifit' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

pod 'Firebase/Core'
pod 'Firebase/Auth'
pod 'GoogleSignIn'

  # Pods for Ellifit
end

We require three frameworks to work with:

  • Firebase Core for initializing Firebase.
  • Firebase Auth for authentication purposes.
  • Google Sign-In for implementing and managing the sign-in/sign-out process.

Now, head back to the terminal and write the following command to install the pods:

pod install

Alternatively, you can use the Swift Package Manager to install these frameworks. It is still in beta, so for this article, we will go with the pod approach.

Now that we’re done setting up our project in Xcode, it is time to move on to creating a Firebase project for our app!

Creating a Firebase project

We need to create an iOS Firebase project to associate with our app. Every time a new user authenticates using Google Sign-In, it will show up in the Firebase console.

Open Firebase Console and select Add Project.

Write the name of the project as Ellifit.

Disable Google Analytics, as we do not require it for this tutorial. Select Create Project. After the project is ready, select Continue. You will be redirected to the Firebase console page. Select the iOS icon to create a new iOS app for the project.

Register the app by providing the same bundle identifier that we created in our Xcode project. Give it whatever nickname you like, and add the App Store ID if you’d like to upload your app to the store later.

Download GoogleService-Info.plist.

Move this file into the root of the Xcode project and add it to all targets.

Select continue until you reach the last step, and then head back to the console.

As this tutorial focuses on adding Google Sign-In support, we need to enable it for our Firebase project. Go to the Authentication page on the Firebase dashboard, and select the Sign-In method tab.

Select Google and toggle to enable it. Change the project public-facing name to Ellifit and the project email to your own email address. Select Save.

Now we’re ready to write some code!

Setting up Firebase SDK

We want users to authenticate with Firebase using their Google accounts in our app. To configure this authentication, open Ellifit.xcworkspace (not Ellifit.xcodeproj) and select the ElliFitApp.swift file. Import Firebase at the top of the file.

import Firebase

Create an extension on EllifitApp and add a setupAuthentication() method.

extension EllifitApp {
  private func setupAuthentication() {
    FirebaseApp.configure()
  }
}

Calling configure() configures a default Firebase app for us. Inside EllifitApp, we create an initializer to set up authentication. The whole struct looks like this:

@main
struct EllifitApp: App {    
  init() {
    setupAuthentication()
  }

  var body: some Scene {
    WindowGroup {
      ContentView()
    }
  }
}

Let’s get started with the next step – setting up Google Sign-In.

Setting up Google Sign-In

Before we write code for the main functionality, go to the project navigator and open GoogleService-Info.plist. Copy the value of the REVERSED_CLIENT_ID.

Now, select the Ellifit project. Select the Ellifit target, and under the Info tab, create a new URL Type. Head back to the EllipfitApp file, and add the following line of code inside setupAuthentication():

GIDSignIn.sharedInstance().clientID = FirebaseApp.app()?.options.clientID

This takes the clientID of our Firebase project from GoogleService-Info.plist and assigns it to the clientID of Google Sign-In.

The next step is to configure the sign-in and sign-out functions.

Configuring Google Sign-In authentication

Create a new Swift file named AuthenticationViewModel.swift, and add the following class to it:

import Firebase
import GoogleSignIn

class AuthenticationViewModel: NSObject, ObservableObject {

  // 1
  enum SignInState {
    case signedIn
    case signedOut
  }

  // 2
  @Published var state: SignInState = .signedOut

  // 3
  override init() {
    super.init()

    setupGoogleSignIn()
  }

  // 4
  func signIn() {
    if GIDSignIn.sharedInstance().currentUser == nil {
      GIDSignIn.sharedInstance().presentingViewController = UIApplication.shared.windows.first?.rootViewController
      GIDSignIn.sharedInstance().signIn()
    }
  }

  // 5
  func signOut() {
    GIDSignIn.sharedInstance().signOut()

    do {
      try Auth.auth().signOut()

      state = .signedOut
    } catch let signOutError as NSError {
      print(signOutError.localizedDescription)
    }
  }

  // 6
  private func setupGoogleSignIn() {
    GIDSignIn.sharedInstance().delegate = self
  }
}

Let’s go through what this code is doing one section at a time:

  1. Create an enum SignInState to define the sign-in and sign-out state for Google Sign-In.
  2. Create a @Published variable to manage the authentication state.
  3. Set up the Google Sign-In function when we initialize the class.
  4. Write a signIn() method that shows the Google Sign-In screen as a model. As we’re not using view controllers to retrieve the presentingViewController, we access it through the shared instance of the UIApplication. Then we call signIn() from the shared instance of the GIDSignIn class to start the sign-in process.
  5. Similar to the process above, we call the signOut() method. We also grab the object that manages authentication in Firebase apps and calls the signOut() method. If there are no errors, we change the state to signedOut.
  6. Finally, we set the delegate of GIDSignIn to self so that AuthenticationViewModel receives the update from this class.

Now, we need to conform to the delegate of GIDSignIn and add the required protocol stubs.

GIDSignInDelegate

Add an extension to AuthenticationViewModel and conform it to GIDSignInDelegate. Add the following code to it:

extension AuthenticationViewModel: GIDSignInDelegate {

  // 1
  func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
    if error == nil {
      firebaseAuthentication(withUser: user)
    } else {
      print(error.debugDescription)
    }
  }

  // 2
  private func firebaseAuthentication(withUser user: GIDGoogleUser) {
    if let authentication = user.authentication {
      let credential = GoogleAuthProvider.credential(withIDToken: authentication.idToken, accessToken: authentication.accessToken)

      Auth.auth().signIn(with: credential) { (_, error) in
        if let error = error {
          print(error.localizedDescription)
        } else {
          self.state = .signedIn
        }
      }
    }
  }
}

When the user finishes the sign-in flow, GIDSignInDelegate calls the sign(_:didSignInFor:withError:) method. If there are no errors, we authenticate the user using Firebase. We get the credential based on the idToken and accessToken from GIDGoogleUser. Finally, we use them to sign in to Firebase.

If there are no errors, we change the state to signedIn.

At the top of EllipfitApp, add the following line:

@StateObject var viewModel = AuthenticationViewModel()

We create a @StateObject variable so that it is alive for the whole lifecycle. We pass it as an environment object into ContentView, so the view has access to the same view model.

Our EllipfitApp will finally look like this:

import SwiftUI
import Firebase
import GoogleSignIn

@main
struct EllifitApp: App {
  @StateObject var viewModel = AuthenticationViewModel()

  init() {
    setupAuthentication()
  }

  var body: some Scene {
    WindowGroup {
      ContentView()
        .environmentObject(viewModel)
    }
  }
}

extension EllifitApp {
  private func setupAuthentication() {
    FirebaseApp.configure()
    GIDSignIn.sharedInstance().clientID = FirebaseApp.app()?.options.clientID
  }
}

Now that we are done implementing Firebase and Google Sign-In in our app, we will design our UI using SwiftUI!

Designing the user interface using SwiftUI

As mentioned earlier, there will be two views for our app:

  • Login view
  • Home view

Login view

Create a new Swift file named LoginView.swift of the type SwiftUI View, and update LoginView with the following:

import SwiftUI

struct LoginView: View {

  // 1
  @EnvironmentObject var viewModel: AuthenticationViewModel

  var body: some View {
    VStack {
      Spacer()

      // 2
      Image("header_image")
        .resizable()
        .aspectRatio(contentMode: .fit)

      Text("Welcome to Ellifit!")
        .fontWeight(.black)
        .foregroundColor(Color(.systemIndigo))
        .font(.largeTitle)
        .multilineTextAlignment(.center)

      Text("Empower your elliptical workouts by tracking every move.")
        .fontWeight(.light)
        .multilineTextAlignment(.center)
        .padding()

      Spacer()

      // 3
      Button("Sign in with Google") {
        viewModel.signIn()
      }
      .buttonStyle(AuthenticationButtonStyle())
    }
  }
}

// 4
struct AuthenticationButtonStyle: ButtonStyle {
  func makeBody(configuration: Self.Configuration) -> some View {
    configuration.label
      .foregroundColor(.white)
      .padding()
      .frame(maxWidth: .infinity)
      .background(Color(.systemIndigo))
      .cornerRadius(12)
      .padding()
  }
}

Here’s the breakdown of this code:

  1. An environment object to receive the AuthenticationViewModel object from the environment. This way, we will only use one instance of the class declared in the SwiftUI lifecycle of the app.
  2. The UI for the onboarding. You can customize it however you want.
  3. A Sign in with Google button that calls the signIn() method from the view model.
  4. A custom ButtonStyle for our button. We will use the style for the sign-in as well as the sign-out button.

Home view

Similar to the previous step, create another Swift file called HomeView.swift and add:

import SwiftUI
import GoogleSignIn

struct HomeView: View {

  // 1
  @EnvironmentObject var viewModel: AuthenticationViewModel

  // 2
  private let user = GIDSignIn.sharedInstance().currentUser

  var body: some View {
    NavigationView {
      VStack {
        HStack {
          // 3
          NetworkImage(url: user?.profile.imageURL(withDimension: 200))
            .aspectRatio(contentMode: .fit)
            .frame(width: 100, height: 100, alignment: .center)
            .cornerRadius(8)

          VStack(alignment: .leading) {
            Text(user?.profile.name ?? "")
              .font(.headline)

            Text(user?.profile.email ?? "")
              .font(.subheadline)
          }

          Spacer()
        }
        .padding()
        .frame(maxWidth: .infinity)
        .background(Color(.secondarySystemBackground))
        .cornerRadius(12)
        .padding()

        Spacer()

        // 4
        Button("Sign out") {
          viewModel.signOut()
        }
        .buttonStyle(AuthenticationButtonStyle())
      }
      .navigationTitle("Ellifit")
    }
    .navigationViewStyle(StackNavigationViewStyle())
  }
}

/// A generic view that is helpful for showing images from the network.
struct NetworkImage: View {
  let url: URL?

  var body: some View {
    if let url = url,
       let data = try? Data(contentsOf: url),
       let uiImage = UIImage(data: data) {
      Image(uiImage: uiImage)
        .resizable()
        .aspectRatio(contentMode: .fit)
    } else {
      Image(systemName: "person.circle.fill")
        .resizable()
        .aspectRatio(contentMode: .fit)
    }
  }
}

Here is what this code is doing:

  1. Declares an @EnvironmentObject similar to what we did in LoginView.
  2. Creates a constant user to access the current user from the shared instance of GIDSignIn.
  3. Uses the constant to access the profile picture, username and email address of the user’s Google account.
  4. Creates a Sign out button that calls the signOut() method from the view model.

We have created the whole UI for the application!

Content view

Now, go to ContentView.swift and update it with the following code:

struct ContentView: View {
  @EnvironmentObject var viewModel: AuthenticationViewModel

  var body: some View {
    switch viewModel.state {
    case .signedIn: HomeView()
    case .signedOut: LoginView()
    }
  }
}

We switch between the state from the view model that updates when a user signs in and signs out. When the user is signed out, we show the LoginView(), and when the user is signed in, the app takes them to the HomeView().

Now, run the app:

Login screen

Click on the Sign in with Google button, and log into your account:

Google screen

After finishing the login process, you will be redirected to the home screen:

Google screen

Click on the Sign out button, and you will be back at the main login screen.

With this, we have completed our fantastic app!

Configuring Firebase on Codemagic

As mentioned earlier, Codemagic provides us with a CI/CD tool for mobile apps. We will be using it to build our app on the integration server. If you’re not a user yet, then sign up for free here:

Sign up

Ignoring confidential file

GoogleService-Info.plist contains the confidential Client_ID that is unique to your app. To prevent it from being misused, it shouldn’t be exposed in a public repository. We need to ignore the file in version control to prevent it from being committed.

Add GoogleService-Info.plist to the .gitignore file in your repository, and commit it before pushing it to remote.

Encrypting confidential file

Codemagic requires the Client_ID to build Firebase on their server as well as our app. You can set up the codemagic.yaml file to build, test and deliver native iOS apps on Codemagic. You can follow this introductory article to create one for our app.

In the Configuration as code section in your iOS project, select Encrypt environment variables. Upload GoogleService-Info.plist to it.

Variable

An encrypted string will be generated. Copy the string, and go to our codemagic.yaml file.

Create a new variable named IOS_SECRET_FIREBASE under var, and paste the string as the variable value.

environment:
  vars:
    IOS_SECRET_FIREBASE: Encrypted(...) # <-- Put the encrypted key here

Adding script

Under script, add the following to decode the file during pre-build.

scripts:
  - name: Decode Google Service file
  script: |
    echo $IOS_SECRET_FIREBASE | base64 --decode > $FCI_BUILD_DIR/Ellifit/GoogleService-Info.plist

After you’re done, save the file.

Start building project

Finally, go to the dashboard, and click on the Start new build button to start building our project.

If you want to generate a release build of your iOS app, then check out the following article:

Conclusion

The Ellifit app is ready for you to log in and start adding code for tracking your workouts. We also tested the app with the excellent tools provided by Codemagic and successfully set up Firebase on it.

You can download the final project from Ellifit’s GitHub repository.

I hope you enjoyed this tutorial! If you found this article helpful, let us know HERE.


About the author: Rudrank is an aspiring technical author and Apple platforms developer. You can mostly find him on Twitter (@rudrankriyam) or on his personal blog (rudrank.blog).

How did you like this article?

Oops, your feedback wasn't sent

Latest articles

Show more posts