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.
Below are the screens of our android example. In the following sections, you can find cloud functions and android code.
Add loan screen.
Loans list screen.
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);
});
});
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);
}
}
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);
}
}
<?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>
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());
}
}
});
}
}
<?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>
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();
}
});
}
}
<?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