ZOFTINO.COM android and web dev tutorials

Android Firebase Cloud Functions Firestore Trigger Example

Firebase cloud functions can be used to build server side applications. Firebase cloud functions can be written in java script or type script and they are triggered in response to certain events. In this post, I’ll show how to create cloud functions which are run in response to events related to inserting, updating and deleting documents in Firestore database from android application with an example.

If you want to know about Firebase cloud functions, how to setup Firebase cloud function project and http-trigger cloud functions, please read Firebase cloud functions tutorial with http-trigger cloud functions and android example.

The example uses Firestore and Firebase authentication. The example app lets users to submit loan requests and save the requests in firestore database. It also has a screen to view list of submitted loan requests and options to perform update and delete on them.

For this example, Firebase cloud functions to handle Firestore database create, update and delete events are created and deployed to Firebase. Create and update functions calculate the loan status and update the loan document in firestore. Delete function stores the deleted loan document in the history collection.

firebase cloud functions firestore example

Below are the screens of our android example. In the following sections, you can find cloud functions and android code.

Add loan screen.

Firebase cloud functions firestore add android example

Loans list screen.

Firebase cloud functions firestore delete update android example

Firebase Cloud Functions Firestore Trigger Example

Firebase cloud functions, triggered by firestore events, can be created using functions.firestore object to handle onDelete, onCreate, onUpdate and onWrite firestore events. These events can be handled for a specific document or any document that matches to a wildcard document path.

In our example, since we want to handle onDelete, onCreate and onUpdate firestore events on any document in loans collection, we need to give loans/{documentId} document path and handle those events as shown in the below code.

In the function, you can get currently affected document from event object passed to the function by calling event.data.data(). In the update event handler, you can get updated data calling event.data.data() and previous data by calling event.data.previous.data().

To add, update, delete other firestore documents in the functions, you need to use firebase-admin node.js module.

'use strict';

const functions = require('firebase-functions');


const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);

exports.addLoanReq = functions.firestore
  .document('loans/{documentId}')
  .onCreate(event => {

	var loanInfo = event.data.data();
	var loanType = loanInfo.loanType;
	var loanAmt = loanInfo.loanAmt;
	var recUserId = loanInfo.userId;
        console.log(`loan amout ${loanAmt}`);
        console.log(`user id ${recUserId}`);

	var loansRef = admin.firestore().collection('loans')

	var loanReqCount;
	loansRef.where('userId', '==', recUserId).get()
    		.then(snapshot => {
		  loanReqCount = snapshot.size;
		  console.log(`no of loan requests ${snapshot.size}`);
    	})
    	.catch(err => {
           console.log('Error getting total request loans', err);
       });

	var statusVal;
	if (loanReqCount > 6){
	  statusVal = 'Loan is rejected due to limit on no of loans';
	}else if(loanAmt < 100000 && loanReqCount < 3){
	   statusVal = 'Loan is accepted for further processing';
	}else if(loanAmt < 50000 && loanReqCount < 4){
	   statusVal = 'Loan is approved, loan office will contact you';
	}else if(loanAmt < 20000 && loanReqCount < 2 && loanType == 'auto'){
	   statusVal = 'Ready to issue disbursment check to auto retailer';
	}else{
	   statusVal = 'Contact loan office at your nearest branch';
	}

        return event.data.ref.set({status: statusVal}, {merge: true});
});
exports.updateLoanReq = functions.firestore
  .document('loans/{documentId}')
  .onUpdate(event => {

	var loanInfo = event.data.data();
	var loanType = loanInfo.loanType;
	var loanAmt = loanInfo.loanAmt;

 	var prevLoanInfo = event.data.previous.data();
	var prevLoanType = prevLoanInfo.loanType;
	var prevLoanAmt = prevLoanInfo.loanAmt;
	console.log(`prev loan amout ${prevLoanAmt}`);
	var prevStatus = prevLoanInfo.status;
	console.log(`prev loan status ${prevStatus}`);
	if ( typeof prevStatus == 'undefined')
	{
  		return;
	}
	var statusVal;

	if(prevStatus.indexOf('rejected') > -1) {
	     statusVal = 'Manual review';
	}else {
	 if (prevLoanAmt > loanAmt){
	  statusVal = 'Loan is accepted for further processing';
	 }else if(prevLoanType == loanType && prevLoanAmt < loanAmt){
	   statusVal = prevStatus;
	 }else{
	   statusVal = 'Contact loan office at your nearest branch';
	 }
	}
        return event.data.ref.set({status: statusVal}, {merge: true});
});
exports.deleteLoanReq = functions.firestore
  .document('loans/{documentId}')
  .onDelete(event => {

        var loanInfo = event.data.previous.data();
	var loanTypeDel = loanInfo.loanType;
	var loanAmtDel = loanInfo.loanAmt;
	var recUserIdDel = loanInfo.userId;

	var addDoc = admin.firestore().collection('loansHistory').add({
        loanType: loanTypeDel,
        loanAmt: loanAmtDel,
	userId: recUserIdDel
        }).then(ref => {
          console.log('Added loan info to history table ', ref.id);
       });

      return addDoc.then(res => {
        console.log('Add: ', res);
      });
});

Base Activity

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;

import zoftino.com.firestore.R;

public class BaseLoanActivity extends AppCompatActivity {
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.loan_menu, menu);
        return true;
    }
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.add_loan_m:
                addLoanRequest();
                return true;
            case R.id.view_loans_m:
                viewLoanRequests();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }
    public void addLoanRequest(){
        Intent i = new Intent();
        i.setClass(this, LoanActivity.class);
        startActivity(i);
    }
    public void viewLoanRequests(){
        Intent i = new Intent();
        i.setClass(this, ViewLoanActivity.class);
        startActivity(i);
    }
}

Add Loan Activity

import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
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.TextView;
import android.widget.Toast;

import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.firestore.DocumentReference;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.SetOptions;

import java.util.HashMap;
import java.util.Map;

import zoftino.com.firestore.EmailPasswordAuthActivity;
import zoftino.com.firestore.R;

public class LoanActivity extends BaseLoanActivity {

    private static final String TAG = "LoanActivity";
    private FirebaseFirestore firestoreDB;
    private FirebaseUser user;
    private String docId = "";

    private EditText loanTypeView;
    private EditText loanAmtView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.loan_activity);

        user = FirebaseAuth.getInstance().getCurrentUser();
        if(user == null){
            Intent i = new Intent();
            i.setClass(this, EmailPasswordAuthActivity.class);
            startActivity(i);
        }

        Toolbar tb = findViewById(R.id.toolbar);
        setSupportActionBar(tb);
        tb.setSubtitle("Add Loan Request");

        firestoreDB = FirebaseFirestore.getInstance();

        Button addButton = findViewById(R.id.add_loan_req);
        loanTypeView = (EditText)findViewById(R.id.loan_type_a);
        loanAmtView = (EditText)findViewById(R.id.loan_amount_a);

        Bundle extras = getIntent().getExtras();

        if (extras != null) {
            ((TextView)findViewById(R.id.loan_type_a)).setText(extras.getString("loanType"));
            ((TextView)findViewById(R.id.loan_amount_a)).setText(extras.getString("loanAmt"));
            addButton.setText("Update Loan");

            docId = extras.getString("docId");
        }

        addButton.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v)
            {
                handleSubmitLoanRequest();
            }
        });


    }
    private void handleSubmitLoanRequest(){
        String loanType = loanTypeView.getText().toString();
        String loanAmt = loanAmtView.getText().toString();

        if(docId.isEmpty()){
            addLoanRequestToFirestoreDb(loanType, loanAmt);
        }else {
            updateDocumentToCollection(loanType, loanAmt);
        }
    }
    private void addLoanRequestToFirestoreDb(String loanType, String loanAmt){
        Map<String, Object> loanData = new HashMap<>();
        loanData.put("loanType", loanType);
        loanData.put("loanAmt", loanAmt);
        loanData.put("userId", user.getUid());

        firestoreDB.collection("loans")
                .add(loanData)
                .addOnSuccessListener(new OnSuccessListener<DocumentReference>() {
                    @Override
                    public void onSuccess(DocumentReference documentReference) {
                        Log.d(TAG, "loan request added "
                                + documentReference.getId());
                        restUi();
                        Toast.makeText(LoanActivity.this,
                                "Loan request has been added",
                                Toast.LENGTH_SHORT).show();
                    }
                })
                .addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception e) {
                        Log.w(TAG, "Error adding loan request", e);
                        Toast.makeText(LoanActivity.this,
                                "Loan request couldn't be added",
                                Toast.LENGTH_SHORT).show();
                    }
                });
    }
    private void updateDocumentToCollection(String loanType, String loanAmt){
        Map<String, Object> loanData = new HashMap<>();
        loanData.put("loanType", loanType);
        loanData.put("loanAmt", loanAmt);

        firestoreDB.collection("loans")
                .document(docId)
                .set(loanData, SetOptions.merge())
                .addOnSuccessListener(new OnSuccessListener<Void>() {
                    @Override
                    public void onSuccess(Void aVoid) {
                        Log.d(TAG, "Loan request updated ");
                        Toast.makeText(LoanActivity.this,
                                "Loan request has been updated",
                                Toast.LENGTH_SHORT).show();
                        showNewLoanRequestScreen();
                    }
                })
                .addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception e) {
                        Log.w(TAG, "Error updating loan request", e);
                        Toast.makeText(LoanActivity.this,
                                "Loan request could not be updated",
                                Toast.LENGTH_SHORT).show();
                    }
                });
    }
    private void restUi(){
        ((TextView)findViewById(R.id.loan_type_a)).setText("");
        ((TextView)findViewById(R.id.loan_amount_a)).setText("");
    }
    private void showNewLoanRequestScreen() {
        Intent i = new Intent();
        i.setClass(this, LoanActivity.class);
        startActivity(i);
    }
}

Add Loan Activity Layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="zoftino.com.firebase.cloudfuncion.LoanActivity">
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary" />
    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp">
        <android.support.design.widget.TextInputLayout
            android:id="@+id/loan_type_la"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent">
            <android.support.design.widget.TextInputEditText
                android:id="@+id/loan_type_a"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Loan type"/>
        </android.support.design.widget.TextInputLayout>
        <android.support.design.widget.TextInputLayout
            android:id="@+id/loan_amount_la"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/loan_type_la">
            <android.support.design.widget.TextInputEditText
                android:id="@+id/loan_amount_a"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Loan Amount"/>
        </android.support.design.widget.TextInputLayout>
        <Button
            android:id="@+id/add_loan_req"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="@style/Widget.AppCompat.Button.Colored"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/loan_amount_la"
            android:text="Add Loan Request"/>
    </android.support.constraint.ConstraintLayout>
</LinearLayout>

View Loan Activity

import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.widget.EditText;

import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.QuerySnapshot;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import zoftino.com.firestore.EmailPasswordAuthActivity;
import zoftino.com.firestore.R;

public class ViewLoanActivity extends BaseLoanActivity {

    private static final String TAG = "ViewLoanActivity";
    private FirebaseFirestore firestoreDB;
    private RecyclerView loandsRecyclerView;
    private FirebaseUser user;

    private EditText loanTypeView;
    private EditText loanAmtView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.view_loans_activity);

        user = FirebaseAuth.getInstance().getCurrentUser();
        if(user == null){
            Intent i = new Intent();
            i.setClass(this, EmailPasswordAuthActivity.class);
            startActivity(i);
        }

        Toolbar tb = findViewById(R.id.toolbar);
        setSupportActionBar(tb);
        tb.setSubtitle("View Loan Requests");

        firestoreDB = FirebaseFirestore.getInstance();

        loandsRecyclerView = findViewById(R.id.loan_req_lst);

        LinearLayoutManager recyclerLayoutManager =
                new LinearLayoutManager(getApplicationContext());
        loandsRecyclerView.setLayoutManager(recyclerLayoutManager);

        DividerItemDecoration dividerItemDecoration =
                new DividerItemDecoration(loandsRecyclerView.getContext(),
                        recyclerLayoutManager.getOrientation());
        loandsRecyclerView.addItemDecoration(dividerItemDecoration);

        getLoanRequests();

    }
    private void getLoanRequests() {
        firestoreDB.collection("loans")
                .whereEqualTo("userId", user.getUid())
                .get()
                .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                    @Override
                    public void onComplete(@NonNull Task<QuerySnapshot> task) {
                        if (task.isSuccessful()) {
                            List<Map<String, String>> loanList =
                                    new ArrayList<Map<String, String>>();
                            Map<String, String> lmap;
                            for(DocumentSnapshot doc : task.getResult()){
                                lmap = new HashMap<String, String>();
                                lmap.put("loanType", doc.getString("loanType"));
                                lmap.put("loanAmt", doc.getString("loanAmt"));
                                lmap.put("status", doc.getString("status"));
                                lmap.put("docId", doc.getId());
                                loanList.add(lmap);
                            }
                            LoansRecyclerViewAdapter recyclerViewAdapter = new
                                    LoansRecyclerViewAdapter(loanList,
                                    ViewLoanActivity.this, firestoreDB);
                            loandsRecyclerView.setAdapter(recyclerViewAdapter);

                        } else {
                            Log.d(TAG, "Error getting loans ", task.getException());
                        }
                    }
                });
    }
}

View Loan 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"
    tools:context="zoftino.com.firebase.cloudfuncion.ViewLoanActivity">
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary" />
    <android.support.v7.widget.RecyclerView
        android:id="@+id/loan_req_lst"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginTop="16dp"/>
</LinearLayout>

Loans List RecyclerView Adapter

import android.content.Context;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.firestore.FirebaseFirestore;

import java.util.List;
import java.util.Map;

import zoftino.com.firestore.R;

public class LoansRecyclerViewAdapter extends
        RecyclerView.Adapter<LoansRecyclerViewAdapter.ViewHolder> {

    private List<Map<String, String>> loandsList;
    private Context context;
    private FirebaseFirestore firestoreDB;

    public LoansRecyclerViewAdapter(List<Map<String, String>> list,
                                    Context ctx, FirebaseFirestore firestore) {
        loandsList = list;
        context = ctx;
        firestoreDB = firestore;
    }
    @Override
    public int getItemCount() {
        return loandsList.size();
    }

    @Override
    public LoansRecyclerViewAdapter.ViewHolder
                    onCreateViewHolder(ViewGroup parent, int viewType) {

        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.loan_item, parent, false);

        LoansRecyclerViewAdapter.ViewHolder viewHolder =
                new LoansRecyclerViewAdapter.ViewHolder(view);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(LoansRecyclerViewAdapter.ViewHolder holder, int position) {
        final int itemPos = position;
        final Map<String, String> item = loandsList.get(position);
        holder.loan_type.setText(item.get("loanType"));
        holder.loan_amt.setText(item.get("loanAmt"));
        holder.loan_status.setText("Status: " + item.get("status"));

        holder.edit.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                editLoanReq(item);
            }
        });

        holder.delete.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                deleteLoanReq(item.get("docId"), itemPos);
            }
        });
    }

    public class ViewHolder extends RecyclerView.ViewHolder {

        public TextView loan_type;
        public TextView loan_amt;
        public TextView loan_status;

        public Button edit;
        public Button delete;

        public ViewHolder(View view) {
            super(view);

            loan_type = (TextView) view.findViewById(R.id.loan_type_i);
            loan_amt = (TextView) view.findViewById(R.id.loan_amt_i);
            loan_status = (TextView) view.findViewById(R.id.loan_status_i);

            edit = view.findViewById(R.id.edit_loan_b);
            delete = view.findViewById(R.id.delete_loan_b);
        }
    }

    private void editLoanReq(Map<String, String> item){
        Intent i = new Intent();
        i.putExtra("loanType", item.get("loanType"));
        i.putExtra("loanAmt", item.get("loanAmt"));
        i.putExtra("docId", item.get("docId"));
        i.setClass(context, LoanActivity.class);
        context.startActivity(i);
    }
    private void deleteLoanReq(String docId, final int position){
        firestoreDB.collection("loans").document(docId).delete()
                .addOnCompleteListener(new OnCompleteListener<Void>() {
                    @Override
                    public void onComplete(@NonNull Task<Void> task) {
                        //remote item from list and refresh
                        loandsList.remove(position);
                        notifyItemRemoved(position);
                        notifyItemRangeChanged(position, loandsList.size());
                        Toast.makeText(context,
                                "Loan request has been deleted",
                                Toast.LENGTH_SHORT).show();
                    }
                });
    }
}

Loan Item Layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_marginTop="4dp"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    tools:context="zoftino.com.firebase.cloudfuncion.ViewLoanActivity">
    <TextView
        android:id="@+id/loan_type_i"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/loan_amt_i"
        app:layout_constraintTop_toTopOf="parent" />
    <TextView
        android:id="@+id/loan_amt_i"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toRightOf="@+id/loan_type_i"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="parent" />
    <TextView
        android:id="@+id/loan_status_i"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        android:textAlignment="center"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/loan_amt_i" />
    <Button
        android:id="@+id/edit_loan_b"
        style="@style/Widget.AppCompat.Button.Colored"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:minHeight="0dp"
        android:minWidth="0dp"
        android:text="Edit"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/delete_loan_b"
        app:layout_constraintTop_toBottomOf="@+id/loan_status_i" />
    <Button
        android:id="@+id/delete_loan_b"
        style="@style/Widget.AppCompat.Button.Colored"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:minHeight="0dp"
        android:minWidth="0dp"
        android:text="Delete"
        app:layout_constraintLeft_toRightOf="@+id/edit_loan_b"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/loan_status_i" />
</android.support.constraint.ConstraintLayout>

To know how to use cloud functions with realtime database trigger, please see Firebase cloud functions realtime database trigger android example

To know how to use cloud functions with cloud storage trigger, please see Firebase cloud functions firestore trigger android example