Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Firebase overview by Web Developer

Oleh Zasadnyy
December 14, 2017

Firebase overview by Web Developer

Firebase is a great tool that helps you rapidly build a product starting from the MVP to test your idea and ending with global scale projects.

In this presentation, I gave a quick overview of tools that are useful for web developer in a current or future projects.

Presented at the local event brought by commit_ community.

Oleh Zasadnyy

December 14, 2017
Tweet

More Decks by Oleh Zasadnyy

Other Decks in Technology

Transcript

  1. <script src=“https://www.gstatic.com/firebasejs/4.8.0/firebase.js”> </script> <script> // Initialize Firebase // TODO: Replace

    with your project's customized code snippet const config = { apiKey: "<API_KEY>", authDomain: "<PROJECT_ID>.firebaseapp.com", databaseURL: "https://<DATABASE_NAME>.firebaseio.com", storageBucket: "<BUCKET>.appspot.com", messagingSenderId: "<SENDER_ID>", }; firebase.initializeApp(config); </script> // initialize a Firebase app
  2. const uiConfig = { signInSuccessUrl: '<url-to-redirect-to-on-success>', signInOptions: [ // Choose

    providers you want. firebase.auth.GoogleAuthProvider.PROVIDER_ID, ], }; // Initialize the FirebaseUI Widget using Firebase. const ui = new firebaseui.auth.AuthUI(firebase.auth()); // The start method will wait until the DOM is loaded. ui.start('#firebaseui-auth-container', uiConfig); // set up a sign-in screen const uiConfig = { signInSuccessUrl: '<url-to-redirect-to-on-success>', signInOptions: [ // Choose providers you want. firebase.auth.GoogleAuthProvider.PROVIDER_ID, ], }; // Initialize the FirebaseUI Widget using Firebase. const ui = new firebaseui.auth.AuthUI(firebase.auth()); // The start method will wait until the DOM is loaded. ui.start('#firebaseui-auth-container', uiConfig);
  3. const uiConfig = { signInSuccessUrl: '<url-to-redirect-to-on-success>', signInOptions: [ // Choose

    providers you want. firebase.auth.GoogleAuthProvider.PROVIDER_ID, ], }; // Initialize the FirebaseUI Widget using Firebase. const ui = new firebaseui.auth.AuthUI(firebase.auth()); // The start method will wait until the DOM is loaded. ui.start('#firebaseui-auth-container', uiConfig); // set up a sign-in screen const uiConfig = { signInSuccessUrl: '<url-to-redirect-to-on-success>', signInOptions: [ // Choose providers you want. firebase.auth.GoogleAuthProvider.PROVIDER_ID, ], }; // Initialize the FirebaseUI Widget using Firebase. const ui = new firebaseui.auth.AuthUI(firebase.auth()); // The start method will wait until the DOM is loaded. ui.start('#firebaseui-auth-container', uiConfig);
  4. initApp = () => { firebase.auth() .onAuthStateChanged((user) => {...}); };

    window.addEventListener('load', function () { initApp() }); // track the Auth state initApp = () => { firebase.auth() .onAuthStateChanged((user) => {...}); }; window.addEventListener('load', function () { initApp() });
  5. initApp = () => { firebase.auth() .onAuthStateChanged((user) => {...}); };

    window.addEventListener('load', function () { initApp() }); // track the Auth state initApp = () => { firebase.auth() .onAuthStateChanged((user) => {...}); }; window.addEventListener('load', function () { initApp() });
  6. const postsRef = firebase.database().ref('posts'); // Retrieve lists of items or

    listen for additions to a list. postsRef.on('child_added', (snapshot) => {...}); // Listen for changes to the items in a list. postsRef.on('child_changed', (snapshot) => {...}); // Listen for items being removed from a list. postsRef.on('child_removed', (snapshot) => {...}); // read data const postsRef = firebase.database().ref('posts'); // Retrieve lists of items or listen for additions to a list. postsRef.on('child_added', (snapshot) => {...}); // Listen for changes to the items in a list. postsRef.on('child_changed', (snapshot) => {...}); // Listen for items being removed from a list. postsRef.on('child_removed', (snapshot) => {...});
  7. const postsRef = firebase.database().ref('posts'); // Retrieve lists of items or

    listen for additions to a list. postsRef.on('child_added', (snapshot) => {...}); // Listen for changes to the items in a list. postsRef.on('child_changed', (snapshot) => {...}); // Listen for items being removed from a list. postsRef.on('child_removed', (snapshot) => {...}); // read data const postsRef = firebase.database().ref('posts'); // Retrieve lists of items or listen for additions to a list. postsRef.on('child_added', (snapshot) => {...}); // Listen for changes to the items in a list. postsRef.on('child_changed', (snapshot) => {...}); // Listen for items being removed from a list. postsRef.on('child_removed', (snapshot) => {...});
  8. const postsRef = firebase.database().ref('posts'); // Return the entire list of

    data. postsRef.on('value', (snapshot) => {...}); // Return the entire list of data ONCE. postsRef.once('value').then((snapshot) => {...}); // read data const postsRef = firebase.database().ref('posts'); // Return the entire list of data. postsRef.on('value', (snapshot) => {...}); // Return the entire list of data ONCE. postsRef.once('value').then((snapshot) => {...});
  9. const postsRef = firebase.database().ref('posts'); // Return the entire list of

    data. postsRef.on('value', (snapshot) => {...}); // Return the entire list of data ONCE. postsRef.once('value').then((snapshot) => {...}); // read data const postsRef = firebase.database().ref('posts'); // Return the entire list of data. postsRef.on('value', (snapshot) => {...}); // Return the entire list of data ONCE. postsRef.once('value').then((snapshot) => {...});
  10. const postsRef = firebase.database().ref('posts'); // Order results by the value

    of a specified child key. postsRef.orderByChild('<CHILD_KEY>').on(...); // Order results by child keys. postsRef.orderByKey().on(...); // Order results by child values. postsRef.orderByValue().on(...); *You can only use one order-by method at a time. // soA data const postsRef = firebase.database().ref('posts'); // Order results by the value of a specified child key. postsRef.orderByChild('<CHILD_KEY>').on(...); // Order results by child keys. postsRef.orderByKey().on(...); // Order results by child values. postsRef.orderByValue().on(...); *You can only use one order-by method at a time.
  11. const postsRef = firebase.database().ref('posts'); // Order results by the value

    of a specified child key. postsRef.orderByChild('<CHILD_KEY>').on(...); // Order results by child keys. postsRef.orderByKey().on(...); // Order results by child values. postsRef.orderByValue().on(...); *You can only use one order-by method at a time. // soA data const postsRef = firebase.database().ref('posts'); // Order results by the value of a specified child key. postsRef.orderByChild('<CHILD_KEY>').on(...); // Order results by child keys. postsRef.orderByKey().on(...); // Order results by child values. postsRef.orderByValue().on(...); *You can only use one order-by method at a time.
  12. const postsRef = firebase.database().ref('posts'); const order = postsRef.orderByChild('<CHILD_KEY>'); // Items

    to return from the beginning of the list. order.limitToFirst(NUMBER).on(..); // Items to return from the end of the list. order.limitToLast(NUMBER).on(..); // Return items greater than or equal to the specified key or value. order.startAt(VALUE).on(..); // Return items less than or equal to the specified key or value. order.endAt(VALUE).on(..); // Return items equal to the specified key or value. order.equalTo(VALUE).on(..); // Blter data const postsRef = firebase.database().ref('posts'); const order = postsRef.orderByChild('<CHILD_KEY>'); // Items to return from the beginning of the list. order.limitToFirst(NUMBER).on(..); // Items to return from the end of the list. order.limitToLast(NUMBER).on(..); // Return items greater than or equal to the specified key or value. order.startAt(VALUE).on(..); // Return items less than or equal to the specified key or value. order.endAt(VALUE).on(..); // Return items equal to the specified key or value. order.equalTo(VALUE).on(..);
  13. const postsRef = firebase.database().ref('posts'); const order = postsRef.orderByChild('<CHILD_KEY>'); // Items

    to return from the beginning of the list. order.limitToFirst(NUMBER).on(..); // Items to return from the end of the list. order.limitToLast(NUMBER).on(..); // Return items greater than or equal to the specified key or value. order.startAt(VALUE).on(..); // Return items less than or equal to the specified key or value. order.endAt(VALUE).on(..); // Return items equal to the specified key or value. order.equalTo(VALUE).on(..); // Blter data const postsRef = firebase.database().ref('posts'); const order = postsRef.orderByChild('<CHILD_KEY>'); // Items to return from the beginning of the list. order.limitToFirst(NUMBER).on(..); // Items to return from the end of the list. order.limitToLast(NUMBER).on(..); // Return items greater than or equal to the specified key or value. order.startAt(VALUE).on(..); // Return items less than or equal to the specified key or value. order.endAt(VALUE).on(..); // Return items equal to the specified key or value. order.equalTo(VALUE).on(..);
  14. const postsRef = firebase.database().ref('posts'); // Generates a new child location

    using a unique key. // Path will be something like '/posts/-IKo28nwJLH0Nc5XeFmj' postsRef.push({...}, () => {}); // Overwrites any data at this location and all child locations. postsRef.child('<POST_ID>').set({...}) .then(() => {}); // Updates only the referenced properties at the current location. postsRef.child('<POST_ID>').update({...}) .then(() => {}); // save data const postsRef = firebase.database().ref('posts'); // Generates a new child location using a unique key. // Path will be something like '/posts/-IKo28nwJLH0Nc5XeFmj' postsRef.push({...}, () => {}); // Overwrites any data at this location and all child locations. postsRef.child('<POST_ID>').set({...}) .then(() => {}); // Updates only the referenced properties at the current location. postsRef.child('<POST_ID>').update({...}) .then(() => {});
  15. const postsRef = firebase.database().ref('posts'); // Generates a new child location

    using a unique key. // Path will be something like '/posts/-IKo28nwJLH0Nc5XeFmj' postsRef.push({...}, () => {}); // Overwrites any data at this location and all child locations. postsRef.child('<POST_ID>').set({...}) .then(() => {}); // Updates only the referenced properties at the current location. postsRef.child('<POST_ID>').update({...}) .then(() => {}); // save data const postsRef = firebase.database().ref('posts'); // Generates a new child location using a unique key. // Path will be something like '/posts/-IKo28nwJLH0Nc5XeFmj' postsRef.push({...}, () => {}); // Overwrites any data at this location and all child locations. postsRef.child('<POST_ID>').set({...}) .then(() => {}); // Updates only the referenced properties at the current location. postsRef.child('<POST_ID>').update({...}) .then(() => {});
  16. { "rules": { "posts": { ".read": true, ".write": "auth !=

    null" } } } // write access if user is authenticated
  17. now The current time in milliseconds since Linux epoch. root

    The root path as it exists before the attempted operation. newData The data as it would exist after the attempted operation. data The data as it existed before the attempted operation. $variables Used to represent ids and dynamic child keys. auth Represents an authenticated user's token payload.
  18. { "rules": { "users": { "$uid": { ".write": "$uid ===

    auth.uid" } } } } // write access if is user own data
  19. { "rules": { "posts": { "body": { ".validate": "newData.isString() &&

    newData.val().length < 140" } } } } // validate data on save
  20. { "chats": { "one": { "title": "Historical Tech Pioneers", "messages":

    { "m1": { "sender": "ghopper", "message": “Hello firebase.” }, "m2": { ... }, // a very long list of messages } }, "two": { ... } } }
  21. // Chats contains only meta info about each conversation //

    stored under the chats's unique ID "chats": { "one": { "title": "Historical Tech Pioneers", "lastMessage": "ghopper: Hello firebase.”, "timestamp": 1459361875666 }, "two": { ... }, "three": { ... } }
  22. // Conversation members are easily accessible // and stored by

    chat conversation ID "members": { // we'll talk about indices like this below "one": { "ghopper": true, "alovelace": true, "eclarke": true }, "two": { ... }, "three": { ... } }
  23. // Messages are separate from data we may want to

    iterate quickly // but still easily paginated and queried, and organized by chat // conversation ID "messages": { "one": { "m1": { "name": "eclarke", "message": "The relay seems to be malfunctioning.", "timestamp": 1459361875337 }, "m2": { ... }, "m3": { ... } }, "two": { ... }, "three": { ... } }
  24. Nested data in documents Subcollections Advantages: Easy to set up

    and streamlines your data structure. Limitations: Can't run queries on nested lists. With larger or growing lists, the document also grows. Advantages: The size of the parent document doesn't change if list grows. Full query capabilities. Limitations: Can’t easily delete subcollections. PeTorm compound queries across subcollections.
  25. const postRef = db.collection('posts').doc('<POST_ID>'); postRef.get() .then((doc) => { if (doc.exists)

    { console.log("Document data:", doc.data()); } else { console.log("No such document!"); } }); // read data from document
  26. const postsRef = db.collection('posts'); postsRef.get() .then((querySnapshot) => { querySnapshot.forEach((doc) =>

    { console.log(doc.id, ' => ', doc.data()); }); }); // read data from collection
  27. const postsRef = db.collection('posts'); postsRef.where('author', '==', 'Oleh') postsRef.where('startsCount', '>', 100)

    postsRef.where('timePosted', '>=', new Date(’01-12-2017')) // Blter data const postsRef = db.collection('posts'); postsRef.where('author', '==', 'Oleh') postsRef.where('startsCount', '>', 100) postsRef.where('timePosted', '>=', new Date(’01-12-2017'))
  28. const postsRef = db.collection('posts'); postsRef.where('author', '==', 'Oleh') postsRef.where('startsCount', '>', 100)

    postsRef.where('timePosted', '>=', new Date(’01-12-2017')) // Blter data const postsRef = db.collection('posts'); postsRef.where('author', '==', 'Oleh') postsRef.where('startsCount', '>', 100) postsRef.where('timePosted', '>=', new Date(’01-12-2017'))
  29. const postsRef = db.collection('posts'); postsRef.orderBy('timePosted').limit(3) postsRef.orderBy('timePosted', 'desc').limit(3) postsRef.orderBy('timePosted').orderBy('starsCount', 'desc') postsRef.where('starsCount',

    ‘>=', 7).orderBy(‘starsCount').limit(3) // order data const postsRef = db.collection('posts'); postsRef.orderBy('timePosted').limit(3) postsRef.orderBy('timePosted', 'desc').limit(3) postsRef.orderBy('timePosted').orderBy('starsCount', 'desc') postsRef.where('starsCount', ‘>=', 7).orderBy(‘starsCount').limit(3)
  30. const postsRef = db.collection('posts'); postsRef.orderBy('timePosted').limit(3) postsRef.orderBy('timePosted', 'desc').limit(3) postsRef.orderBy('timePosted').orderBy('starsCount', 'desc') postsRef.where('starsCount',

    ‘>=', 7).orderBy(‘starsCount').limit(3) // order data const postsRef = db.collection('posts'); postsRef.orderBy('timePosted').limit(3) postsRef.orderBy('timePosted', 'desc').limit(3) postsRef.orderBy('timePosted').orderBy('starsCount', 'desc') postsRef.where('starsCount', ‘>=', 7).orderBy(‘starsCount').limit(3)
  31. const postsRef = db.collection('posts'); // Add a new document with

    a generated id. postsRef.add({...}) .then(() => {}); // Overwrites any data at this location and all child locations. postsRef.doc('<POST_ID>').set({...}) .then(() => {}); // Updates only the referenced properties at the current location. postsRef.doc('<POST_ID>').update({...}) .then(() => {}); // save data const postsRef = db.collection('posts'); // Add a new document with a generated id. postsRef.add({...}) .then(() => {}); // Overwrites any data at this location and all child locations. postsRef.doc('<POST_ID>').set({...}) .then(() => {}); // Updates only the referenced properties at the current location. postsRef.doc('<POST_ID>').update({...}) .then(() => {});
  32. const postsRef = db.collection('posts'); // Add a new document with

    a generated id. postsRef.add({...}) .then(() => {}); // Overwrites any data at this location and all child locations. postsRef.doc('<POST_ID>').set({...}) .then(() => {}); // Updates only the referenced properties at the current location. postsRef.doc('<POST_ID>').update({...}) .then(() => {}); // save data const postsRef = db.collection('posts'); // Add a new document with a generated id. postsRef.add({...}) .then(() => {}); // Overwrites any data at this location and all child locations. postsRef.doc('<POST_ID>').set({...}) .then(() => {}); // Updates only the referenced properties at the current location. postsRef.doc('<POST_ID>').update({...}) .then(() => {});
  33. service cloud.firestore { match /databases/{database}/documents { // Rules match specific

    paths match /myCollection/myDocument { allow read, write: if <condition>; } // Rules can also specify a wildcard match /myCollection/{anyDocument} { allow write: if <other_condition>; } } } // restrict access
  34. service cloud.firestore { match /databases/{database}/documents { match /posts { allow

    read; allow write: if request.auth != null; } } } // write access if user is authenticated
  35. service cloud.firestore { match /databases/{database}/documents { match /users/{userId} { allow

    read; allow write: if request.auth.uid == userId; } } } // write access if is user own data
  36. service cloud.firestore { match /databases/{database}/documents { match /posts/{post} { allow

    write: if request.resource.data.body is string && request.resource.data.body.length < 140; } } } // validate data on save
  37. service cloud.firestore { match /databases/{database}/documents { match /posts/{post} { function

    canSavePost() { return request.resource.data.body is string && request.resource.data.body.length < 140; } allow write: if canSavePost() ; } } } // validate data on save service cloud.firestore { match /databases/{database}/documents { match /posts/{post} { function canSavePost() { return request.resource.data.body is string && request.resource.data.body.length < 140; } allow write: if canSavePost() ; } } }
  38. service cloud.firestore { match /databases/{database}/documents { match /posts/{post} { function

    canSavePost() { return request.resource.data.body is string && request.resource.data.body.length < 140; } allow write: if canSavePost() ; } } } // validate data on save service cloud.firestore { match /databases/{database}/documents { match /posts/{post} { function canSavePost() { return request.resource.data.body is string && request.resource.data.body.length < 140; } allow write: if canSavePost() ; } } }
  39. Realtime DB Firestore Data is harder to organize at scale.

    Queries are deep by default: They always return the entire subtree. Write data as an individual operation. Easier to organize at scale (subcollections in documents). Query subcollections within a document instead of an entire collection. Batch operations and complete them atomically.
  40. Realtime DB Firestore Read and write rules cascade. Offline support

    for mobile clients on iOS and Android only. Stability you'd expect from a battle-tested, tried-and-true product. Rules don't cascade unless you use a wildcard. Offline support for iOS, Android, and web clients. Stability in a beta product is not always the same as that of a fully launched product.
  41. If you're comfortable with a product in beta, use Cloud

    Firestore for your new projects. - Firebase team
  42. "hosting": { // Add the "rewrites" section within "hosting" "rewrites":

    [ { "source": "**", "destination": "/index.html" } ] } // Brebase.json
  43. // Create a root reference const storageRef = firebase.storage().ref(); //

    Create a reference to 'images/mountains.jpg' const mountainImagesRef = storageRef.child('images/mountains.jpg'); mountainImagesRef.put(file) .then((snapshot) => {...}); // upload Ble
  44. const uploadTask = storageRef.child('mountains.jpg').put(file); // Pause the upload uploadTask.pause(); //

    Resume the upload uploadTask.resume(); // Cancel the upload uploadTask.cancel(); // control upload
  45. const uploadTask = storageRef.child('mountains.jpg').put(file); // 1. 'state_changed' observer, called any

    time the state changes // 2. Error observer, called on failure // 3. Completion observer, called on successful completion uploadTask.on('state_changed', (snap) => { const progress = (snap.bytesTransferred / snap.totalBytes) * 100; }, (error) => { // Handle unsuccessful uploads }, () => { // Handle successful uploads on complete const downloadURL = uploadTask.snapshot.downloadURL; }); // listen to changes const uploadTask = storageRef.child('mountains.jpg').put(file); // 1. 'state_changed' observer, called any time the state changes // 2. Error observer, called on failure // 3. Completion observer, called on successful completion uploadTask.on('state_changed', (snap) => { const progress = (snap.bytesTransferred / snap.totalBytes) * 100; }, (error) => { // Handle unsuccessful uploads }, () => { // Handle successful uploads on complete const downloadURL = uploadTask.snapshot.downloadURL; });
  46. const uploadTask = storageRef.child('mountains.jpg').put(file); // 1. 'state_changed' observer, called any

    time the state changes // 2. Error observer, called on failure // 3. Completion observer, called on successful completion uploadTask.on('state_changed', (snap) => { const progress = (snap.bytesTransferred / snap.totalBytes) * 100; }, (error) => { // Handle unsuccessful uploads }, () => { // Handle successful uploads on complete const downloadURL = uploadTask.snapshot.downloadURL; }); // listen to changes const uploadTask = storageRef.child('mountains.jpg').put(file); // 1. 'state_changed' observer, called any time the state changes // 2. Error observer, called on failure // 3. Completion observer, called on successful completion uploadTask.on('state_changed', (snap) => { const progress = (snap.bytesTransferred / snap.totalBytes) * 100; }, (error) => { // Handle unsuccessful uploads }, () => { // Handle successful uploads on complete const downloadURL = uploadTask.snapshot.downloadURL; });
  47. // The Cloud Functions for Firebase SDK. const functions =

    require('firebase-functions'); // The Firebase Admin SDK to access the Firebase Realtime Database. const admin = require('firebase-admin'); admin.initializeApp(functions.config().firebase); // index.js
  48. functions.database.ref('/messages/{pushId}/original') .onWrite(event => { // Grab the value of what

    was written to the Realtime Database. const original = event.data.val(); console.log('Uppercasing', event.params.pushId, original); const uppercase = original.toUpperCase(); // Setting an "uppercase" sibling in the Realtime Database. return event.data.ref.parent.child('uppercase').set(uppercase); }); // uppercase message functions.database.ref('/messages/{pushId}/original') .onWrite(event => { // Grab the value of what was written to the Realtime Database. const original = event.data.val(); console.log('Uppercasing', event.params.pushId, original); const uppercase = original.toUpperCase(); // Setting an "uppercase" sibling in the Realtime Database. return event.data.ref.parent.child('uppercase').set(uppercase); });
  49. functions.database.ref('/messages/{pushId}/original') .onWrite(event => { // Grab the value of what

    was written to the Realtime Database. const original = event.data.val(); console.log('Uppercasing', event.params.pushId, original); const uppercase = original.toUpperCase(); // Setting an "uppercase" sibling in the Realtime Database. return event.data.ref.parent.child('uppercase').set(uppercase); }); // uppercase message functions.database.ref('/messages/{pushId}/original') .onWrite(event => { // Grab the value of what was written to the Realtime Database. const original = event.data.val(); console.log('Uppercasing', event.params.pushId, original); const uppercase = original.toUpperCase(); // Setting an "uppercase" sibling in the Realtime Database. return event.data.ref.parent.child('uppercase').set(uppercase); });
  50. // Retrieve Firebase Messaging object. const messaging = firebase.messaging(); messaging.requestPermission()

    .then(() => { console.log('Notification permission granted.'); // ... }); // client index.js
  51. messaging.setBackgroundMessageHandler((payload) => { console.log('Received background message ', payload); // Customize

    notification here const notificationTitle = 'Background Message Title'; const notificationOptions = { body: 'Background Message body.', icon: '/firebase-logo.png' }; return self.registration.showNotification(notificationTitle, notificationOptions); }); // Brebase-messaging-sw.js messaging.setBackgroundMessageHandler((payload) => { console.log('Received background message ', payload); // Customize notification here const notificationTitle = 'Background Message Title'; const notificationOptions = { body: 'Background Message body.', icon: '/firebase-logo.png' }; return self.registration.showNotification(notificationTitle, notificationOptions); });
  52. messaging.setBackgroundMessageHandler((payload) => { console.log('Received background message ', payload); // Customize

    notification here const notificationTitle = 'Background Message Title'; const notificationOptions = { body: 'Background Message body.', icon: '/firebase-logo.png' }; return self.registration.showNotification(notificationTitle, notificationOptions); }); // Brebase-messaging-sw.js messaging.setBackgroundMessageHandler((payload) => { console.log('Received background message ', payload); // Customize notification here const notificationTitle = 'Background Message Title'; const notificationOptions = { body: 'Background Message body.', icon: '/firebase-logo.png' }; return self.registration.showNotification(notificationTitle, notificationOptions); });
  53. /functions-project-12345 /followers /followedUid-12345 followerUid-67890: true /users /Uid-12345 displayName: "Bob Dole"

    /notificationTokens 1234567890: true photoURL: "https://lh3.googleusercontent.com/..."
  54. functions.database.ref('/followers/{followedUid}/{followerUid}') .onWrite(event => { const followerUid = event.params.followerUid; const followedUid

    = event.params.followedUid; // Get the list of device notification tokens. const deviceTokensPromise = admin.database() .ref(`/users/${followedUid}/notificationTokens`).once('value'); // Get the follower profile. const followerProfilePromise = admin.auth().getUser(followerUid); // ... return admin.messaging().sendToDevice(tokens, payload); }); // Brebase function index.js functions.database.ref('/followers/{followedUid}/{followerUid}') .onWrite(event => { const followerUid = event.params.followerUid; const followedUid = event.params.followedUid; // Get the list of device notification tokens. const deviceTokensPromise = admin.database() .ref(`/users/${followedUid}/notificationTokens`).once('value'); // Get the follower profile. const followerProfilePromise = admin.auth().getUser(followerUid); // ... return admin.messaging().sendToDevice(tokens, payload); });
  55. functions.database.ref('/followers/{followedUid}/{followerUid}') .onWrite(event => { const followerUid = event.params.followerUid; const followedUid

    = event.params.followedUid; // Get the list of device notification tokens. const deviceTokensPromise = admin.database() .ref(`/users/${followedUid}/notificationTokens`).once('value'); // Get the follower profile. const followerProfilePromise = admin.auth().getUser(followerUid); // ... return admin.messaging().sendToDevice(tokens, payload); }); // Brebase function index.js functions.database.ref('/followers/{followedUid}/{followerUid}') .onWrite(event => { const followerUid = event.params.followerUid; const followedUid = event.params.followedUid; // Get the list of device notification tokens. const deviceTokensPromise = admin.database() .ref(`/users/${followedUid}/notificationTokens`).once('value'); // Get the follower profile. const followerProfilePromise = admin.auth().getUser(followerUid); // ... return admin.messaging().sendToDevice(tokens, payload); });