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

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.

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.

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.

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.

Objectives

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.

Note: Because Firebase acts as an intermediary, the frontend and backend clients
are loosely coupled. This means that iOS, Android, and web versions of the Playchat
app can share the same backend service and Firebase project.

Costs

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.

Creating a Firebase project

Note: If you have already run the
Android version
of this sample, you can
reuse that Firebase project instead of creating a new one. To do so, skip the
first three steps below and instead open the
Firebase console,
click the existing Firebase project, and select Add app.

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

Click Next in the Add Firebase SDK section.

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

Note: The AppDelegate class already includes the Firebase initialization
code.

Click Next in the Add initialization code section.

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

Creating a Realtime Database

From the left menu of the
Firebase console,
select Database in the Develop group.

In the Database page, go to the Realtime Database section and click
Create database.

In the Security rules for Realtime Database dialog, select Start in
test mode and click Enable.

Caution: Test mode allows anyone with your database reference to perform
read and write operations to your database. If test mode isn't appropriate
for your purposes, you can write security rules to manage access to your
data. For more information, see
Get Started with Database Rules
in the Firebase documentation.

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.

Make a note of the Firebase URL for your project, which is in the form
https://[FIREBASE_PROJECT_ID].firebaseio.com/ 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.

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

Note: If you don't see a left menu, make sure you have the Playchat project selected.

Click Set up sign-in method.

Select Google, turn the Enable toggle on, 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.

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

Select Service accounts and then Manage all service accounts.

Click CREATE SERVICE ACCOUNT.

Configure the following settings:

In Service account name, enter playchat-servlet.

In Role, select Project > Owner.

Caution: The Owner role gives the service account full access to all
resources in the project. In a production app, use the role that
provides the minimum required access to your service account.

Check Furnish a new private key.

Select JSON for Key Type.

Click Create.

Download the JSON key file for the service account and save 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.

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.

Enabling billing and APIs for the Google Cloud Platform project

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

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.

Provide the credentials that the gcloud tool uses to access GCP.

gcloud auth login

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]

Verify that the project has been set by listing the configuration.

gcloud config list

(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.

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

mvn clean package appengine:run

Note: If you have installed the gcloud command-line tool
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.

If you are prompted Do you want the application "Python.app" 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

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

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].appspot.com/printLogs,
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

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

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

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

Running and testing the iOS app

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

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

Select the books channel.

Enter 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.

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.

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.

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].appspot.com/printLogs, 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.

Note: If you are running the backend service in the App Engine flexible
environment, several servlets are running concurrently. You might have to
reload the page for your backend service a couple of times until the
servlet tracking your logs is the one that responds to the page load
request.

Exploring the code

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

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.

On the backend service side, when a servlet instance starts, the
init(ServletConfig config) function in MessageProcessorServlet.java
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.

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.

Cleaning 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 GCP Console, because
the Firebase and GCP projects are one and the same.

Caution: Deleting a project has the following effects:

Everything in the project is deleted. If you used an existing project for
this tutorial, when you delete it, you also delete any other work you've done in the project.

Custom project IDs are lost.
When you created this project, you might have created a custom project ID that you want to use in
the future. To preserve the URLs that use the project ID, such as an appspot.com
URL, delete selected resources inside the project instead of deleting the whole project.

If you plan to explore multiple tutorials and quickstarts, reusing projects can help you avoid
exceeding project quota limits.

Click the checkbox next to the non-default app version you want to
delete.
Note:
The only way you can delete the default version of your App Engine app is by deleting your
project. However, you can stop the default version in the
GCP Console. This action shuts down all instances associated with the
version. You can restart these instances later if needed.

In the App Engine standard environment, you can stop the default version only if your app has
manual or basic scaling.

Click the Delete button at the top of the page to
delete the app version.

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.