Build an iOS App Using Firebase and the App Engine Flexible Environment

Stay organized with collections Save and categorize content based on your preferences.

This tutorial demonstrates how to write an iOS app with backend data storage, real-time synchronization, and user-event logging using Firebase. Java servlets running in the App Engine flexible environment listen for new user logs stored in Firebase and process them.

The instructions show how to do this using Firebase and the App Engine flexible environment.

If you want your app to process user data or orchestrate events, you can extend Firebase with the App Engine flexible environment to perform automatic real-time data synchronization.

The sample app, Playchat, stores chat messages in the Firebase Realtime Database, which automatically synchronizes that data across devices. Playchat also writes user-event logs to Firebase. To learn more about how the database synchronizes data, see How does it work? in the Firebase documentation.

The following diagram shows the Playchat client architecture.

Playchat client architecture

A set of Java servlets running in the App Engine flexible environment register as listeners with Firebase. The servlets respond to new user-event logs and process the log data. The servlets use transactions to ensure that only one servlet handles each user-event log.

The following diagram shows the Playchat server architecture.

Playchat server architecture

Communication between the app and the servlet happens in three parts:

  • When a new user logs into Playchat, the app requests a logging servlet for that user by adding an entry under /inbox/ in the Firebase Realtime Database.

  • One of the servlets accepts the assignment by updating the value of the entry to its servlet identifier. The servlet uses a Firebase transaction to guarantee that it is the only servlet that can update the value. After the value is updated, all other servlets ignore the request.

  • When the user logs in, logs out, or changes to a new channel, Playchat logs the action in /inbox/[SERVLET_ID]/[USER_ID]/, where [SERVLET_ID] is the identifier of the servlet instance and [USER_ID] is a hash value representing the user.

  • The servlet watches the inbox for new entries and collects the log data.

In this sample app, the servlets copy the log data locally and display it on a web page. In a production version of this app, the servlets could process the log data or copy it to Cloud Storage, Cloud Bigtable, or BigQuery for storage and analysis.


This tutorial demonstrates how to:

  • Build an iOS app, Playchat, that stores data in the Firebase Realtime Database.

  • Run a Java servlet in the App Engine flexible environment that connects to Firebase and receives notifications when the data stored in Firebase changes.

  • Use these two components to build a distributed, streaming, backend service to collect and process log data.


Firebase has a free level of usage. If your usage of these services is less than the limits specified in the Firebase free plan there is no charge for using Firebase.

Instances within the App Engine flexible environment are charged the cost of the underlying Compute Engine virtual machines.

Before you begin

Install the following software:

Install the App Engine Java component of the gcloud CLI by running the following command in a Terminal window.

gcloud components install app-engine-java

Cloning the sample code

  1. Clone the client app code.

    git clone
  2. Clone the backend servlet code.

    git clone

Creating a Firebase project

  1. Create a Firebase account or log into an existing account.

  2. Click Add project.

  3. In Project name, enter: Playchat. Make a note of the Project ID assigned to your project, it's used in multiple steps in this tutorial.

  4. Follow the remaining setup steps and click Create project.

  5. After the wizard provisions your project, click Continue.

  6. In the Overview page of your project, click the Settings gear and then click Project settings.

  7. Click Add Firebase to your iOS app.

  8. In iOS bundle ID, enter:

  9. Click Register app.

  10. Follow the steps in the Download config file section to add the GoogleService-Info.plist file to the PlayChat folder in your project.

  11. Click Next in the Download config file section.

  12. Make a note of the instructions to use CocoaPods to install and manage project dependencies. The CocoaPods dependency manager is already configured in the sample code.

  13. Run the following command to install the dependencies. The command might take a long time to complete.

    pod install

    You might need to run pod repo update if your CocoaPods installation cannot find the Firebase dependency.

    After this step, use the newly created .xcworkspace file instead of the .xcodeproj file for all future development on the iOS app.

  14. Click Next in the Add Firebase SDK section.

  15. Make a note of the code required to initialize Firebase in your project.

  16. Click Next in the Add initialization code section.

  17. Click Skip this step in the Run your app to verify installation section.

Creating a Realtime Database

  1. From the Firebase console, select your project.

  2. From the left menu of the console, select Realtime Database in the Build group.

  3. Click Create database in the Realtime Database section.

  4. Select a location close to you.

  5. In the Security rules dialog, select Start in test mode and then click Enable.

    This step displays the data you’ve stored in Firebase. In later steps of this tutorial, you can revisit this web page to see data added and updated by the client app and backend servlet.

  6. In the Rules tab of the database, make sure you have the security rules for read/write that specify a date 30 days in the future with the unit of time defined in Unix epoch time. For example, if you are creating the rule on August 4, the rule should expire after September 4:

      "rules": {
        ".read": "now < 1659639249000", //2022-08-04
        ".write": "now < 1659639249000" //2022-08-04
  7. Make a note of the Firebase URL for your project, which is in the form https://[FIREBASE_PROJECT_ID] and appears next to a link icon.

Enabling Google authentication for the Firebase project

There are a variety of sign-in providers you can configure to connect to your Firebase project. This tutorial walks you through setting up authentication so that the user can sign in using a Google Account.

  1. From the left menu of the Firebase console, click Authentication in the Build group.

  2. Click Set up sign-in method or Get Started if it's your first time to the page.

  3. Under the Sign-in method tab, select Google, turn the Enable toggle on, select the support email, and click Save.

Adding a service account to the Firebase project

The backend servlet doesn't use a Google Account to sign in with. Instead, it uses a service account to connect to Firebase. The following steps walk you through creating a service account that can connect to Firebase and adding the service account credentials to the servlet code.

  1. From the left menu of the Firebase console, next to the Playchat project home, select the Settings gear and then Project settings.

  2. Select Service accounts tab and then click the Manage service account permissions link.

  3. Click Create Service Account.

  4. Configure the following settings:

    1. In Service account name, enter playchat-servlet and then click Create And Continue.
    2. In Select a role, select Project > Owner and then click Continue.

    3. Click Done.

  5. Click the service account you just created, under the KEYS tab, click Add Key, and then click Create new key.

  6. Next to Key Type, click JSON, and then click Create to download the key.

  7. Save the downloaded JSON key file for the service account to the backend service project, firebase-appengine-backend, in the src/main/webapp/WEB-INF/ directory. The filename is in the form Playchat-[UNIQUE_ID].json.

  8. Edit src/main/webapp/WEB-INF/web.xml and edit the initialization parameters as follows:

    • Replace JSON_FILE_NAME with the name of the JSON key file you downloaded.

    • Replace FIREBASE_URL with the Firebase URL you noted earlier.


Enabling billing and APIs for the Google Cloud project

For the backend service to run on GCP, you need to enable billing and APIs for the project. The Cloud project is the same project you created in Creating a Firebase project and has the same project identifier.

  1. In the Google Cloud console, select the Playchat project.

    Go to the Projects page

  2. Make sure that billing is enabled for your Cloud project. Learn how to check if billing is enabled on a project.

  3. Enable the App Engine Admin and Compute Engine APIs.

    Enable the APIs

Building and deploying the backend service

The backend service in this sample uses a Docker configuration to specify its hosting environment. This customization means you must use the App Engine flexible environment instead of the App Engine standard environment.

To build the backend servlet and deploy it in the App Engine flexible environment, you can use the Google App Engine Maven plugin. This plugin is already specified in the Maven build file included with this sample.

Setting the project

For Maven to build the backend servlet correctly, you must provide it the Google Cloud Platform (GCP) project to launch the servlet resources into. The GCP project identifier and the Firebase project identifier are the same.

  1. Provide the credentials that the gcloud CLI uses to access GCP.

    gcloud auth login
  2. Set the project to your Firebase project with the following command, replacing [FIREBASE_PROJECT_ID] with the name of the Firebase project ID you noted previously.

    gcloud config set project [FIREBASE_PROJECT_ID]
  3. Verify that the project has been set by listing the configuration.

    gcloud config list
  4. If this is the first time you're using App Engine, initialize your App Engine app:

    gcloud app create

(Optional) Running the service on the local server

When you develop a new backend service, run the service locally before you deploy it to App Engine to rapidly iterate changes without the overhead of a full deployment to App Engine.

When you run the server locally, it doesn’t use a Docker configuration or run in an App Engine environment. Instead, Maven guarantees all dependent libraries are installed locally and the app runs on the Jetty web server.

  1. In the firebase-appengine-backend directory, build and run the backend module locally with the following command:

    mvn clean package appengine:run

    If you have installed the Google Cloud CLI to a directory other than ~/google-cloud-sdk, add the installation path to the command as shown in the following, replacing [PATH_TO_TOOL] with your custom path.

    mvn clean package appengine:run -Dgcloud.gcloud_directory=[PATH_TO_TOOL]
  2. If you are prompted Do you want the application "" to accept incoming network connections?, select Allow.

When the deployment ends, open http://localhost:8080/printLogs to verify that your backend service is running. The web page displays Inbox : followed by a 16-digit identifier. This is the inbox identifier for the servlet running on your local machine.

As you refresh the page, this identifier doesn't change; your local server spins up a single servlet instance. This is useful for testing, because there is only one servlet identifier stored in the Firebase Realtime Database.

To shut down the local server, enter Ctrl+C.

Deploying the service to the App Engine flexible environment

When you run the backend service in the App Engine flexible environment, App Engine uses the configuration in /firebase-appengine-backend/src/main/webapp/Dockerfiles to build the hosting environment that the service runs in. The flexible environment spins up several servlet instances and scales them up and down to meet demand.

  • In the firebase-appengine-backend directory, build and run the backend module locally with the following command:

    mvn clean package appengine:deploy
    mvn clean package appengine:deploy -Dgcloud.gcloud_directory=[PATH_TO_GCLOUD]

As the build runs, you see the lines “Sending build context to Docker daemon…”. The previous command uploads your Docker configuration and sets it up in the App Engine flexible environment.

When the deployment ends, open https://[FIREBASE_PROJECT_ID], where [FIREBASE_PROJECT_ID] is the identifier from Create a Firebase project. The web page that displays Inbox : followed by a 16-digit identifier. This is the inbox identifier for a servlet running in the App Engine flexible environment.

As you refresh the page, this identifier periodically changes as App Engine spins up multiple servlet instances to handle incoming client requests.

Updating the URL scheme in the iOS sample

  1. In Xcode, with the PlayChat workspace open, open the PlayChat folder.

  2. Open GoogleService-Info.plist and copy the value of REVERSED_CLIENT_ID.

  3. Open Info.plist and navigate to key URL types > Item 0 (Editor) > URL Schemes > Item 0.

  4. Replace the placeholder value, [REVERSED_CLIENT_ID], with the value you copied from GoogleService-Info.plist.

Running and testing the iOS app

  1. In Xcode, with the PlayChat workspace open, select Product > Run.

  2. When the app is loaded onto the simulator, sign in with your Google Account.

    Sign in to Playchat

  3. Select the books channel.

  4. Enter a message.

    Send a message

When you do, the Playchat app stores your message in the Firebase Realtime Database. Firebase synchronizes the data stored in the database across devices. Devices running Playchat display the new message when a user selects the books channel.

Send a message

Verifying the data

After you’ve used the Playchat app to generate some user events using the Playchat app, you can verify that the servlets are registering as listeners and collecting user-event logs.

Open the Firebase Realtime Database for your app, where [FIREBASE_PROJECT_ID] is the identifier from Create a Firebase project.[FIREBASE_PROJECT_ID]/database/data

At the bottom of the Firebase Realtime Database, under the /inbox/ data location, there is a group of nodes prefixed by client- and followed by a randomly generated key that represents a user’s account login. The last entry in this example, client-1240563753, is followed by a 16-digit identifier of the servlet currently listening to log events from that user, in this example 0035806813827987.

Data stored in Firebase Realtime Database

Immediately above, under the /inbox/ data location, are servlet identifiers for all currently assigned servlets. In this example, only one servlet is collecting logs. Under /inbox/[SERVLET_IDENTIFIER] are the user logs written by the app to that servlet.

Open the App Engine page for your backend service at https://[FIREBASE_PROJECT_ID], where [FIREBASE_PROJECT_ID] is the identifier from Creating a Firebase project. The page displays the identifier for the servlet that recorded the user events you generated. You can also see the log entries for those events below the servlet’s inbox identifier.

Exploring the code

The Playchat iOS app defines a class, FirebaseLogger, that it uses to write user-event logs to the Firebase Realtime Database.

import Firebase

class FirebaseLogger {
  var logRef: DatabaseReference!

  init(ref: DatabaseReference!, path: String!) {
    logRef = ref.child(path)

  func log(_ tag: String!, message: String!) {
    let entry: LogEntry = LogEntry(tag: tag, log: message)

When a new user logs in, Playchat calls the requestLogger function to add a new entry to the /requestLogger/ location in the Firebase Realtime Database and set a listener so Playchat can respond when a servlet updates the value of that entry, accepting the assignment.

When a servlet updates the value, Playchat removes the listener and writes the “Signed in” log to the servlet’s inbox.

func requestLogger() {
  ref.child(IBX + "/" + inbox!).removeValue()
  ref.child(IBX + "/" + inbox!)
    .observe(.value, with: { snapshot in
      if snapshot.exists() {
        self.fbLog = FirebaseLogger(ref: self.ref, path: self.IBX + "/"
          + String(describing: snapshot.value!) + "/logs")
        self.ref.child(self.IBX + "/" + self.inbox!).removeAllObservers()
        self.msgViewController!.fbLog = self.fbLog
        self.fbLog!.log(self.inbox, message: "Signed in")

On the backend service side, when a servlet instance starts, the init(ServletConfig config) function in connects to the Firebase Realtime Database and adds a listener to the /inbox/ data location.

When a new entry is added to the /inbox/ data location, the servlet updates the value with its identifier, a signal to the Playchat app that the servlet accepts the assignment to process logs for that user. The servlet uses Firebase transactions to ensure that only one servlet can update the value and accept the assignment.

 * Receive a request from a client and reply back its inbox ID.
 * Using a transaction ensures that only a single servlet instance replies
 * to the client. This lets the client know to which servlet instance
 * send consecutive user event logs.
firebase.child(REQLOG).addChildEventListener(new ChildEventListener() {
  public void onChildAdded(DataSnapshot snapshot, String prevKey) {
    firebase.child(IBX + "/" + snapshot.getValue()).runTransaction(new Transaction.Handler() {
      public Transaction.Result doTransaction(MutableData currentData) {
        // Only the first servlet instance writes its ID to the client inbox.
        if (currentData.getValue() == null) {
        return Transaction.success(currentData);

      public void onComplete(DatabaseError error, boolean committed, DataSnapshot snapshot) {}
  // ...

After a servlet has accepted an assignment to process a user’s event logs, it adds a listener that detects when the Playchat app writes a new log file to the servlet’s inbox. The servlet responds by retrieving the new log data from the Firebase Realtime Database.

 * Initialize user event logger. This is just a sample implementation to
 * demonstrate receiving updates. A production version of this app should
 * transform, filter, or load to another data store such as Google BigQuery.
private void initLogger() {
  String loggerKey = IBX + "/" + inbox + "/logs";
  firebase.child(loggerKey).addChildEventListener(new ChildEventListener() {
    public void onChildAdded(DataSnapshot snapshot, String prevKey) {
      if (snapshot.exists()) {
        LogEntry entry = snapshot.getValue(LogEntry.class);

    public void onCancelled(DatabaseError error) {

    public void onChildChanged(DataSnapshot arg0, String arg1) {}

    public void onChildMoved(DataSnapshot arg0, String arg1) {}

    public void onChildRemoved(DataSnapshot arg0) {}

Clean up

To avoid incurring charges to your Google Cloud Platform account for the resources used in this tutorial:

Delete the Google Cloud Platform and Firebase project

The simplest way to stop billing charges is to delete the project you created for this tutorial. Although you created the project in the Firebase console, you can also delete it in the Google Cloud console, because the Firebase and Cloud projects are one and the same.

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

Delete non-default versions your App Engine app

If you don't want to delete your GCP and Firebase project, you can reduce costs by deleting the non-default versions of your App Engine flexible environment app.

  1. In the Google Cloud console, go to the Versions page for App Engine.

    Go to Versions

  2. Select the checkbox for the non-default app version that you want to delete.
  3. To delete the app version, click Delete.

What's next

  • Analyze and archive data — In this sample, the servlets store log data only in memory. To extend this sample, you could have the servlets archive, transform, and analyze the data, using services such as Cloud Storage, Cloud Bigtable, Google Cloud Dataflow, and BigQuery.

  • Evenly distribute the workload across servlets — App Engine provides both automatic and manual scaling. With automatic scaling, the flexible environment detects changes in the workload and responds by adding or removing VM instances in the cluster. With manual scaling, you specify a static number of instances to handle traffic. For more information about how to configure scaling, see Service scaling settings in the App Engine documentation.

    Because user activity logs are assigned to the servlets through accessing the Firebase Realtime Database, the workload might not be evenly distributed. For example, one servlet might process more user-event logs than other servlets.

    You can improve efficiency by implementing a workload manager that independently controls workload on each VM. This workload balancing could be based on metrics like logging requests per second or number of concurrent clients.

  • Recover unprocessed user-event logs — In this sample implementation, if a servlet instance crashes, the client app associated with that instance continues to send log events to the servlet’s inbox in the Firebase Realtime Database. In a production version of this app, the backend service must detect this situation to recover the unprocessed user-event logs.

  • Implement additional features using Cloud AI products — Explore how to provide ML-based features with Cloud AI products and services. For example, you can extend this sample implementation to provide a speech translation feature using a combination of the Speech-to-Text, Translation, and Text-to-Speech APIs. For more information, see Adding speech translation to your Android app.