ZOFTINO.COM android and web dev tutorials

Android Firebase Cloud Functions Cloud Storage Trigger Example

In this post, you can learn how to use Firebase cloud functions which are run in response to uploading, modifying and deleting cloud storage files from android application.

I suggest reading pervious post Firebase cloud functions tutorial with http-trigger cloud functions and android example to know about Firebase cloud functions, how to setup Firebase cloud function project and how to create http-trigger cloud functions.

We will use example android app called notes to learn how to create and use cloud-storage-triggered firebase cloud functions. The example app lets users to enter notes, save them, view list of previous notes and update and delete notes. Each time user submits notes, text file containing notes is uploaded to cloud storage.

In the application, user is provided with an option to view list of saved notes, this data comes from firestore database. For each item in the list of notes, user can perform edit or delete operations. Edit option takes user to edit notes screen showing the selected notes text downloaded from cloud storage and allows user to modify and save it. Delete operation deletes the selected notes file from cloud storage.

In this example, cloud functions to handle cloud-storage file upload, modify and delete events is used to add, update and delete notes-file information in the Firestore database.

For add, update and delete notes operations in the android application, cloud-function gets called due to changes in cloud storage. The cloud function will perform add, update or delete file information in the firestore database depending on the android application operation.

To know about Firebase cloud storage, please read Firebase cloud storage tutorial with android example.

As the examples also uses firestore database and firebase authentication, please read Firestore database tutorial and Firebase authentication tutorial. In these posts, you can also find information on how to secure files in cloud storage and data in firestore database using authentication and security rules.

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

Cloud Functions Cloud Storage Android Example Upload

firebase cloud functions firestore trigger android example upload files

Cloud Functions Cloud Storage Android Example Delete & Update

firebase cloud functions firestore trigger android example modify delete files

Cloud Storage Trigger Cloud Functions

'use strict';

const functions = require('firebase-functions');
const path = require('path');
const currdate = require('current-date');

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

exports.metadata = functions.storage.object().onChange(event => {
  const fileInfo = event.data;
  const filePath = fileInfo.name;
  const resourceState = fileInfo.resourceState;
  const customMetadata = fileInfo.metadata;

   //get custom metadata used to know new or modified file
  const customMetadataUpdate = customMetadata['update'];

  const fileName = path.basename(filePath);

  console.log(`file path ${filePath}`);
  console.log(`file name ${fileName}`);
  console.log(`custom metadata ${customMetadataUpdate}`);

//file deleted
  if (resourceState === 'not_exists') {
    console.log('handling file delete event.');
    var deleteDoc = admin.firestore().collection('notes')
        .where('notesFile', '==', filePath)
	.get()
        .then(snapshot => {
            snapshot.forEach(doc => {
                console.log(doc.id, '=>', doc.data());
		doc.ref.delete();
            });
        })
        .catch(err => {
            console.log('Error getting file info for deletion', err);
        });
    return deleteDoc.then(res => {
        console.log('Deleted file info from db ', res);
    });
  }

  var currDate = currdate('date');
  console.log(`current date ${currDate}`);  

//file modified
  if (customMetadataUpdate && resourceState === 'exists' && customMetadataUpdate === 'y') {
    console.log('handling update file event');
    var updateDoc = admin.firestore().collection('notes')
        .where('notesFile', '==', filePath)
        .get()
        .then(snapshot => {
            snapshot.forEach(doc => {
                console.log(doc.id, '=>', doc.data());
		doc.ref.set({modifiedDt: currDate,}, {merge: true});
            });
        })
        .catch(err => {
            console.log('Error getting document for update', err);
        });
    return updateDoc.then(res => {
        console.log('Updated modified date', res);
    });
  }

//new file
  console.log('handling new file event.');

  const notesName = getNotesName(fileName);
  console.log(`notes name ${notesName}`); 
  const userId = getUserId(filePath);
  console.log(`user id ${userId}`); 


  var addDoc = admin.firestore().collection('notes').add({
        notesFile: filePath,
        notesName: notesName,
	createDt: currDate,
	modifiedDt: currDate,
	userId: userId
        }).then(ref => {
          console.log('Added file info to db', ref.id);
  });

  return addDoc.then(res => {
     console.log('Added file info', res);
  });

});
function getNotesName(fileName){
  var arr = fileName.split("-");
  var fileNme = '';
  for(var i=0;i<arr.length-1;i++) {
    fileNme = fileNme + ' ' +arr[i];
  }
   return fileNme;
}
function getUserId(filePath){  
  var arr = filePath.split("/");
  return arr[1];
}

Activity

import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;

import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;

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

public class NotesActivity extends AppCompatActivity {

    private FragmentManager fm;
    private FirebaseUser user;

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

        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("Storage Cloud Functions");

        fm = getSupportFragmentManager();
        addNotesFrgmt();
    }
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.notes_menu, menu);
        return true;
    }
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.add_notes_m:
                addNotesFrgmt();
                return true;
            case R.id.view_notes_m:
                viewNotesFrgmt();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }
    public void addNotesFrgmt(){
        FragmentTransaction ft = fm.beginTransaction();
        ft.replace(R.id.notes_frame, new CreateNotesFragment());
        ft.commit();
    }
    public void viewNotesFrgmt(){
        FragmentTransaction ft = fm.beginTransaction();
        ft.replace(R.id.notes_frame, new ViewNotesFragment());
        ft.commit();
    }
}

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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <FrameLayout
        android:id="@+id/notes_frame"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="8dp"
        android:layout_marginStart="8dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/toolbar"/>
</LinearLayout>

Add Fragment

import android.content.Intent;
import android.os.Bundle;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
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.storage.FirebaseStorage;
import com.google.firebase.storage.StorageMetadata;
import com.google.firebase.storage.StorageReference;
import com.google.firebase.storage.UploadTask;

import java.io.UnsupportedEncodingException;

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

public class CreateNotesFragment extends Fragment {

    private static final String TAG = "CreateNotesFragment";

    private StorageReference storageRef;
    private String editNotesFile = null;
    private FirebaseUser user;
    private String userPath;

    private EditText notesNameET;
    private EditText notesET;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        View view = inflater.inflate(R.layout.notes_create, container, false);

        user = FirebaseAuth.getInstance().getCurrentUser();
        if (user == null) {
            Intent i = new Intent();
            i.setClass(getActivity(), EmailPasswordAuthActivity.class);
            startActivity(i);
        }
        userPath = "notes/" + user.getUid() + "/";
        FirebaseStorage firebaseStorage = FirebaseStorage.getInstance();
        storageRef = firebaseStorage.getReference();

        Button button = view.findViewById(R.id.add_notes_b);
        notesET = view.findViewById(R.id.notes);
        notesNameET = view.findViewById(R.id.note_name);

        //view notes for editing
        String notesName = null;
        if (getArguments() != null) {
            editNotesFile = getArguments().getString("notesFile");
            notesName = getArguments().getString("notesName");
        }
        if (editNotesFile != null) {
            notesNameET.setText(notesName);
            ((TextView) view.findViewById(R.id.notes_head)).setText("Edit Notes");
            button.setText("Update Notes");
            downloadNotesFromCloudStorage(editNotesFile);
        }

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (editNotesFile == null) {
                    //new notes
                    addNotes();
                } else {
                    //update existing notes
                    updateNotes();
                }

            }
        });

        return view;
    }

    public void addNotes() {
        String noteName = notesNameET.getText().toString();
        String notes = notesET.getText().toString();

        uploadNotesFileToCloudStorage(noteName, notes);
    }

    private void uploadNotesFileToCloudStorage(String notesName, String notes) {

        byte[] notesBytes = getBytesNoteText(notes);

        if (notesBytes == null) {
            Toast.makeText(getActivity(),
                    "Error in processing notes, please retry",
                    Toast.LENGTH_SHORT).show();
            return;
        }
        String fileName = getFileName(notesName);

        uploadFile(fileName, notesBytes, false);
    }

    private String getFileName(String notesName) {
        String fileName = notesName.replaceAll(" ", "-");
        fileName = userPath + fileName + "-" + SystemClock.elapsedRealtime() + ".txt";
        return fileName;
    }

    private byte[] getBytesNoteText(String notes) {
        byte[] notesBytes = null;
        try {
            notesBytes = notes.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            Log.e(TAG, "Error encoding file", e);
        }
        return notesBytes;
    }

    private void uploadFile(String fileName, byte[] notesBytes, boolean isModification) {
        StorageReference notesFileRef = storageRef.child(fileName);
        UploadTask uploadTask;

        if (isModification) {
            StorageMetadata metadata = new StorageMetadata.Builder()
                    .setCustomMetadata("update", "y")
                    .build();
            uploadTask = notesFileRef.putBytes(notesBytes, metadata);
        } else {
            uploadTask = notesFileRef.putBytes(notesBytes);
        }
        uploadTask.addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception exception) {
                Toast.makeText(getActivity(),
                        "Error in saving notes, please retry",
                        Toast.LENGTH_SHORT).show();
            }
        }).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
            @Override
            public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
                showNotesScreen();
                Toast.makeText(getActivity(),
                        "Notes saved.",
                        Toast.LENGTH_SHORT).show();
            }
        });
    }

    private void updateNotes() {
        String notes = notesET.getText().toString();
        byte[] notesBytes = getBytesNoteText(notes);

        if (notesBytes == null) {
            Toast.makeText(getActivity(),
                    "Error in updating notes, please retry",
                    Toast.LENGTH_SHORT).show();
            return;
        }
        uploadFile(editNotesFile, notesBytes, true);
    }

    private void downloadNotesFromCloudStorage(String fileName) {
        StorageReference storageReference = storageRef.child(fileName);
        final long ONE_MEGABYTE = 1024 * 1024;
        storageReference.getBytes(ONE_MEGABYTE).addOnSuccessListener(
                new OnSuccessListener<byte[]>() {
                    @Override
                    public void onSuccess(byte[] bytes) {
                        String downloadedNotes = new String(bytes);
                        notesET.setText(downloadedNotes);
                    }
                }).addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception exception) {
                Toast.makeText(getActivity(),
                        "Error downloading notes.",
                        Toast.LENGTH_SHORT).show();
                showNotesScreen();
            }
        });
    }

    private void showNotesScreen() {
        Intent i = new Intent();
        i.setClass(getActivity(), NotesActivity.class);
        startActivity(i);
    }
}

Add Fragment 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_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginLeft="8dp"
    android:layout_marginRight="8dp"
    tools:context="zoftino.com.firebase.cloudfuncion.storage.NotesActivity">
    <TextView
        android:id="@+id/notes_head"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="Create Notes"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"  />
    <EditText
        android:id="@+id/note_name"
        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/notes_head"
        android:hint="Notes name"/>
    <EditText
        android:id="@+id/notes"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="text|textMultiLine"
        android:lines="8"
        android:scrollbars="vertical"
        android:gravity="top|left"
        android:scrollHorizontally="false"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/note_name"
        android:hint="Enter notes"/>
    <Button
        android:id="@+id/add_notes_b"
        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/notes"
        android:text="Add Notes"/>
</android.support.constraint.ConstraintLayout>

View Fragment

import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

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 ViewNotesFragment extends Fragment {
    private static final String TAG = "ViewNotesFragment";
    private FirebaseFirestore firestoreDB;
    private FirebaseUser user;
    private RecyclerView notesRecyclerView;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.notes_view,
                container, false);

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

        notesRecyclerView = (RecyclerView) view.findViewById(R.id.notes_lst);

        LinearLayoutManager recyclerLayoutManager =
                new LinearLayoutManager(getActivity().getApplicationContext());
        notesRecyclerView.setLayoutManager(recyclerLayoutManager);

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

        getNotesListFromDB();

        return view;
    }

    private void getNotesListFromDB() {
        firestoreDB.collection("notes")
                .whereEqualTo("userId", user.getUid())
                .get()
                .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                    @Override
                    public void onComplete(@NonNull Task<QuerySnapshot> task) {
                        if (task.isSuccessful()) {
                            List<Map<String, String>> notesList =
                                    new ArrayList<Map<String, String>>();
                            Map<String, String> notesMap;
                            for(DocumentSnapshot doc : task.getResult()){
                                notesMap = new HashMap<String, String>();
                                notesMap.put("notesFile", doc.getString("notesFile"));
                                notesMap.put("notesName", doc.getString("notesName"));
                                notesMap.put("createDt", doc.getString("createDt"));
                                notesList.add(notesMap);
                            }
                            NotesListRecyclerViewAdapter recyclerViewAdapter = new
                                    NotesListRecyclerViewAdapter(notesList, getActivity());
                            notesRecyclerView.setAdapter(recyclerViewAdapter);

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

View Fragment 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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginLeft="8dp"
    android:layout_marginRight="8dp">
    <TextView
        android:id="@+id/notes_view_head"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        android:text="Notes"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"  />
    <android.support.v7.widget.RecyclerView
        android:id="@+id/notes_lst"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/notes_view_head"/>
</android.support.constraint.ConstraintLayout>

RecyclerView Adapter

import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.FragmentManager;
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.storage.FirebaseStorage;
import com.google.firebase.storage.StorageReference;

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

import zoftino.com.firestore.R;


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

    private List<Map<String, String>> notesList;
    private Context context;

    private StorageReference storageRef;

    public NotesListRecyclerViewAdapter(List<Map<String, String>> list, Context ctx) {
        notesList = list;
        context = ctx;

        FirebaseStorage firebaseStorage = FirebaseStorage.getInstance();
        storageRef = firebaseStorage.getReference();
    }
    @Override
    public int getItemCount() {
        return notesList.size();
    }
    @Override
    public NotesListRecyclerViewAdapter.ViewHolder
    onCreateViewHolder(ViewGroup parent, int viewType) {

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

        NotesListRecyclerViewAdapter.ViewHolder viewHolder =
                new NotesListRecyclerViewAdapter.ViewHolder(view);
        return viewHolder;
    }
    @Override
    public void onBindViewHolder(NotesListRecyclerViewAdapter.ViewHolder holder,
                                 int position) {
        final int itemPos = position;
        final Map<String, String> notes = notesList.get(position);
        holder.notesName.setText(notes.get("notesName"));
        holder.createdDate.setText(notes.get("createDt"));

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

        holder.delete.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                deleteNotes(notes.get("notesFile"), itemPos);
            }
        });
    }
    public class ViewHolder extends RecyclerView.ViewHolder {
        public TextView notesName;
        public TextView createdDate;

        public Button edit;
        public Button delete;

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

            notesName = (TextView) view.findViewById(R.id.notes_name);
            createdDate = (TextView) view.findViewById(R.id.create_date);

            edit = view.findViewById(R.id.view_note_b);
            delete = view.findViewById(R.id.delete_note_b);
        }
    }
    private void editNotes(Map<String, String> notes) {
        FragmentManager fm = ((NotesActivity) context).getSupportFragmentManager();

        Bundle bundle = new Bundle();
        bundle.putString("notesFile",notes.get("notesFile"));
        bundle.putString("notesName",notes.get("notesName"));

        CreateNotesFragment createFragment = new CreateNotesFragment();
        createFragment.setArguments(bundle);

        fm.beginTransaction().replace(R.id.notes_frame, createFragment).commit();
    }
    private void deleteNotes(String fileName, final int position) {
        StorageReference deleteRef = storageRef.child(fileName);
        deleteRef.delete().addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                notesList.remove(position);
                notifyItemRemoved(position);
                notifyItemRangeChanged(position, notesList.size());
                Toast.makeText(context,
                        "Notes file has been deleted",
                        Toast.LENGTH_SHORT).show();
            }
        });
    }
}

RecyclerView 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"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="8dp"
    android:layout_marginLeft="8dp"
    android:layout_marginRight="8dp">
    <TextView
        android:id="@+id/notes_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <TextView
        android:id="@+id/create_date"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/notes_name" />
    <Button
        android:id="@+id/view_note_b"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:minHeight="0dp"
        android:minWidth="0dp"
        style="@style/Widget.AppCompat.Button.Colored"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/delete_note_b"
        app:layout_constraintTop_toBottomOf="@+id/create_date"
        android:text="View"/>
    <Button
        android:id="@+id/delete_note_b"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:minHeight="0dp"
        android:minWidth="0dp"
        style="@style/Widget.AppCompat.Button.Colored"
        app:layout_constraintLeft_toRightOf="@+id/view_note_b"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/create_date"
        android:text="Delete"/>
</android.support.constraint.ConstraintLayout>

Cloud Functions Logs

firebase cloud functions firestore trigger example logs

Cloud Storage Files

 firebase cloud functions cloud storage trigger example files

Firestore Data

 firebase cloud functions cloud storage trigger example firestore data

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

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