ZOFTINO.COM android and web dev tutorials

Android Server Side Functionality Using Firebase Cloud Functions

For certain features of your app, if running code on the server instead of on the device or client makes sense then the easiest way to provide the server side features is by using Firebase cloud functions. With Firebase Cloud Functions, all you need to do to have the server side functionality is to write functions and deploy them.

Google cloud functions for firebase allows you to deploy functions written in java script or type script, the deployed functions will be connected to event providers defined in the functions and these functions will be run in response to the events which satisfy conditions defined in the functions.

In this post, you can learn about setting up Firebase cloud functions project, cloud functions triggers, writing cloud functions, using node.js modules, deploying cloud functions and using cloud functions in android.

Setting Up Cloud Functions Project

To manage and deploy Firebase cloud functions project, Firebase command line interface needs to be installed on development machine. Below are the steps to complete the required setup.

To install Firebase CLI, NodeJS and npm needs to be installed. To install both Node.js and npm, go to Node Js website and download Node.js windows installer package, run it to get setup wizard and click next.

node js setup wizard

Accept license by checking the check box and click next.

node js setup wizard license

Choose destination folder and click next.

node js setup wizard destination folder

Select the way features are installed, if you don’t need documentation short cuts select the option from the drop down and then click next.

node js setup wizard select the way features are installed

Then click install to complete node.js and npm installation.

node js setup install

Then install Firebase command line by opening command prompt and running below command.

npm install -g firebase-tools

Then login to Firebase from firebase command line by entering firebase login command in command prompt. It will open browser, enter your firebase account credentials and login.

firebase command line login

Once login is successful, create cloud functions project by going to the directory where you want to create the project and entering below command.

 firebase init functions 

It will prompt you to select existing Firebase project or create new project. Once project is selected, it will ask you to select language for writing cloud functions. It provides JavaScript and TypeScript options. Once language option is chosen, it will ask you if you want to install dependencies with npm. Choose no, if you want to install required dependencies manually, see deploying cloud functions section for more information. Finally it creates cloud functions project.

Below is the structure of firebase cloud functions project. The main source file is index.js. This is where you will define your cloud function importing required Node.js modules.

npm cloud functions project structure

With that, Firebase cloud functions project setup is complete and now you can start writing cloud functions.

Cloud Function Triggers

Before we start writing cloud functions, we need to know about the triggers which run the functions. Cloud functions are run in response to http requests, Firebase Firestore events, cloud pub/sub events, cloud storage events, analytics events, crashlytics events, realtime database events and authentication events.

When you write functions, you need to use the object exported by Cloud Functions for Firebase SDK that listens to the event provider that you are targeting. For example, to run your cloud function in response to http requests, you need to use functions.https provided by The Cloud Functions for Firebase SDK.

Similarly, if your cloud function needs to be run in response to cloud pub/sub events use functions.pubsub object, to run in response to storage events use functions.storage, in response to crashlytics events use functions.crashlytics object, in response to analytics events use functions.analytics object, in response to authentication events use functions.auth object, in response to realtime database use functions.database object and in response to firestore events use functions.firestore object.

Writing Cloud Functions

You can write your Firebase cloud functions in index.js file which is the main node.js source file. You can create java script functions in separate files and import and use them in index.js. There are several useful Node.js modules which make creating cloud functions easy. For example validator module for validating strings, moment module for handling dates and express modules provide web framework for developing websites, etc.

Let’s see an example of a cloud function. The example cloud function will be triggered in response to http requests and it validates the credit card number sent in http request. It can handle both Http Get and Post requests. The function uses validator module. Add this function to index.js and deploy it as described in the following section.

 const moment = require('validator');
exports.isCreditCard = functions.https.onRequest((req, res) => {
  if (req.method === 'PUT') {
    res.status(403).send('Forbidden!');
  }
  cors(req, res, () => {
    let ccnum = req.query.ccnum;
    if (!ccnum) {
       ccnum = req.body.ccnum;
    }
    let msg;
    if(validator().isCreditCard(ccnum)){
	msg = 'Valid credit card';
    }else{
        msg = 'Invalid credit card';
    }
    res.status(200).send(msg);
  });
});

Deploying Cloud Functions

You can deploy cloud functions to Firebase server using firebase deploy command.

firebase deploy

To deploy only specific functions, you can use below command specifying function name to be deployed. You can test your functions by using URL for each function that prints after the deployment.

firebase deploy --only functions:validateString
firebase cloud functions deployment

You may get Cannot find module error like the error message shown below while trying to deploy.

Error: Error parsing triggers: Cannot find module 'firebase-functions'

To fix it, you need to install missing node.js modules by using bellow command from functions directory.

npm install

To install specific node.js module, you need to use g option, for example to install express cors module below is the command.

npm install -g cors

These commands download modules into node_modules folder in functions directory. Firebase command line will ignore node_module folder when functions are deployed, so you need to declare dependencies in metadata file called package.json that exists in functions directory. You can define dependencies either manually or using npm save command, for example to add validator module dependency to package.json run below command.

npm install –save validator

So it is important that the node.js modules which you use in your cloud functions are added to package.json file. Below is an example package.json file with dependencies.

 {
  "name": "functions",
  "description": "Cloud Functions for Firebase",
  "scripts": {
    "serve": "firebase serve --only functions",
    "shell": "firebase experimental:functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "dependencies": {
    "cors": "^2.8.4",
    "firebase-admin": "~5.4.2",
    "firebase-functions": "^0.7.1",
    "validator": "^9.2.0"
  },
  "private": true
}
 

Monitoring Cloud Functions

To view deployed cloud functions, login to Firebase console , click functions in the left navigation and view cloud functions dashboard.

Firebase cloud functions deployed functions

You can use console.log and console.error statements to log information and errors in cloud functions. You can view the logs in Firebase console.

Firebase cloud functions logs

Firebase Cloud Functions Android Example

Below android example shows how to use of firebase cloud function in android. The example lets users enter the order amount to know the discount information. App sends https request to Firebase function using OkHttp. Firebase function calculates discount using the order amount sent in http request and returns the discount information to app.

firebase cloud functions android example

Firebase Cloud Function Http

For the android example, firebase cloud function that is trigged by http request is defined and deployed to firebase.

 const functions = require('firebase-functions');
const cors = require('cors')({origin: true});
const validator = require('validator');
exports.discount = functions.https.onRequest((req, res) => {
  if (req.method === 'PUT') {
    res.status(403).send('Forbidden!');
  }
  cors(req, res, () => {
    let amount = req.query.orderAmt;
    if (!amount) {
       amount = req.body.orderAmt;
    }
    let discount;
    if(validator.isNumeric(amount)){
	discount = 'Invalid Order Amount';
    }else{
	if (amount < 200){
	  discount = 'Eligible for extra 10% off';
	}else if(amount > 199 && amount < 400){
	  discount = 'Eligible for extra 15% off';
	}else if(amount > 399 && amount < 600){
	  discount = 'Eligible for extra 20% off';
	}else{
	  discount = 'Eligible for extra 25% off';
	}
    }
    res.status(200).send(discount);
});
});

Activity

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import java.io.IOException;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import zoftino.com.firestore.R;

public class OrderDiscountActivity extends AppCompatActivity {

    private static final String TAG = "OrderDiscountActivity";

    private String FIREBASE_CLOUD_FUNCTION_URL
            = "https://us-central1-yourfirebaseproject.cloudfunctions.net/discount";
    private EditText orderaAmt;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.order_dicount_layout);

        Toolbar tb = findViewById(R.id.toolbar);
        setSupportActionBar(tb);

        orderaAmt = findViewById(R.id.order_amt);
        Button discountButton = findViewById(R.id.order_amt_b);
        discountButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                findDiscountAmt();
            }
        });
    }

    public void findDiscountAmt() {
        String orderAmt = orderaAmt.getText().toString();
        double orderAmount;
        try {
            orderAmount = Double.parseDouble(orderAmt);
        }catch (Exception e){
            orderaAmt.setError("Enter valid amount");
            return;
        }
        sendMessageToFcm(orderAmount);
    }

    private void sendMessageToFcm(double orderAmount) {

        OkHttpClient httpClient = new OkHttpClient();

            HttpUrl.Builder httpBuider =
                    HttpUrl.parse(FIREBASE_CLOUD_FUNCTION_URL).newBuilder();
            httpBuider.addQueryParameter("orderAmt", ""+orderAmount);

            Request request = new Request.Builder().
                    url(httpBuider.build()).build();

            httpClient.newCall(request).enqueue(new Callback() {
                @Override public void onFailure(Call call, IOException e) {
                    Log.e(TAG, "error in getting response from firebase cloud function");

                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(OrderDiscountActivity.this,
                                    "Cound't get response from cloud function",
                                    Toast.LENGTH_SHORT).show();
                        }
                    });
                }
                @Override public void onResponse(Call call, Response response){
                    ResponseBody responseBody = response.body();
                    String resp = "";
                    if (!response.isSuccessful()) {
                        Log.e(TAG, "fail response from firebase cloud function");
                        Toast.makeText(OrderDiscountActivity.this,
                                "Cound't get response from cloud function",
                                Toast.LENGTH_SHORT).show();
                    }else {
                        try {
                            resp = responseBody.string();
                        } catch (IOException e) {
                            resp = "Problem in getting discount info";
                            Log.e(TAG, "Problem in reading response " + e);
                        }
                    }
                    runOnUiThread(responseRunnable(resp));
                }
            });
    }
    private Runnable responseRunnable(final String responseStr){
        Runnable resRunnable = new Runnable(){
            public void run(){
                Toast.makeText(OrderDiscountActivity.this
                        ,responseStr,
                        Toast.LENGTH_SHORT).show();
            }
        };
        return resRunnable;
    }
}

Activity Layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center_horizontal"
    tools:context="zoftino.com.firebase.cloudfuncion.OrderDiscountActivity">
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary" />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="4dp"
        android:layout_marginRight="4dp"
        android:textAppearance="@style/TextAppearance.AppCompat.Headline"
        android:text="Enter Order Amt &amp; Find Discount"
        android:layout_marginTop="16dp"/>
    <android.support.design.widget.TextInputLayout
        android:id="@+id/order_amt_l"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="4dp"
        android:layout_marginRight="4dp">
        <android.support.design.widget.TextInputEditText
            android:id="@+id/order_amt"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType= "numberDecimal"
            android:hint="Enter Order Amount"/>
    </android.support.design.widget.TextInputLayout>
    <Button
        android:id="@+id/order_amt_b"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        style="@style/Widget.AppCompat.Button.Colored"
        android:text="Know Discount Amount"/>
</LinearLayout>