1

I'm a Swift dev and I'm not a backend dev. This is actually 3 different questions in bold below but they all are dependent upon each other. Any stack overflow answers to similar questions would be enough to get me started

@followers
      |
    kim_userId // kimKardashian
          -userId_0: 1
           -... // every user in between
           -userId_188_million: 1

Right now I'm using a very inefficient way to send a mass push notification:

@IBAction func postButtonTapped(button: UIButton) {

    let postsRef = Database.database().reference().child("posts").child(kim_userId).child(postId)
    postsRef.updateChildValues(postDictionary, withCompletionBlock: { (err, _)

        if let error = error { return }

        // post was successful now send a push notification to all of these followers
        self.fetchFollowers(for: kim_userId, send: postId)
    })
}

func fetchFollowers(for userId: String, send newPostId: String) {

    let followersRef = Database.database().reference().child("followers").child(userId)
    followersRef.observe(.childAdded) { (snapshot) in

        let userId = snapshot.key

        self.fetchDeviceToken(forFollower: userId, send: newPostId)          
    }
}

func fetchDeviceToken(forFollower userId: String, send newPostId: String) {

    let usersRef = Database.database().reference().child("users").child(userId)
    usersRef.observeSingleEvent(of .value) { (snapshot) in

        guard let dict = snapshot.value as? [String: Any] else { return }

        guard let deviceToken = dict["deviceToken"] as? String else { return }

        self.sendPushNotification(toFollower: userId, with: deviceToken, send: newPostId)
    }
}

func sendPushNotification(toFollower: userId, with deviceToken: String, send newPostId: String) {
      
    var apsDict = [String: Any]()
    // newPostId and whatever other values added to the dictionary

    guard let url = URL(string: "https://fcm.googleapis.com/fcm/send") else { return }

    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.httpBody = try? JSONSerialization.data(withJSONObject: apsDict, options: [])
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.setValue("key=\(my_serverKey...)", forHTTPHeaderField: "Authorization")
    let task = URLSession.shared.dataTask(with: request)  { (data, response, error) in
        do {
            if let jsonData = data {
                if let jsonDataDict = try JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.allowFragments) as? [String: AnyObject] {
                    print("Received data:\n\(jsonDataDict))")
                }
            }
        } catch let err as NSError {
            print(err.debugDescription)
        }
    }
    task.resume()
}

For eg Kim Kardashian has 188 million followers on Instagram, when she posts something it goes out to all of her followers at once. The way I'm currently doing it is not the way to go. I'm pretty sure this is a situation for Cloud Functions but I do not know enough about Cloud Functions so I am looking at where to start.

-how do I connect with Cloud Functions from within an iOS app?

-no matter what I have to get each follower from the "followers" ref and then I have to get each follower's deviceToken from within their "users" ref, I'm not sure where to start here

-how do I actually send a push notification code once inside Cloud Functions? I found this answer but it's in javascript. I don't know javascript but I do know a tad bit of Node.js

PostVC:

@IBAction func postButtonTapped(button: UIButton) {

    let postsRef = Database.database().reference().child("posts").child(kim_userId).child(postId)
    postsRef.updateChildValues(postDictionary, withCompletionBlock: { (err, _)

        if let error = error { return }

        // post was successful now connect to Cloud Functions so that a mass push notification can be sent

        self.codeToConnectWithCloudFunctions(for: kim_userId, send: postId)
    })
}

func codeToConnectWithCloudFunctions(for userId: String, send newPostId: String) {

    // 1. how do I get each of her followers
    // 2. how do I get each of their deviceTokens
    // 3. how do I send the push notification
}

Any links with similar answers are enough to get me started. I can do more digging from there and ask a more specific question based on whatever I find

Lance Samaria
  • 17,576
  • 18
  • 108
  • 256

2 Answers2

0

How do I connect with Cloud Functions from within an iOS app?

You can call Cloud Functions by doing the same thing that you would do calling any API, you will have to design your Cloud Functions to be Callable Functions, you can find more details on them and how to set it all up, in Swift and other languages, in this Documentation.

No matter what I have to get each follower from the "followers" ref and then I have to get each follower's deviceToken from within their "users" ref, I'm not sure where to start here and how do I actually send a push notification code once inside Cloud Functions? I found this answer but it's in javascript. I don't know javascript but I do know a tad bit of Node.js

These 2 questions can be answered together by this community answer that states that you should integrate Firebase Cloud Messaging into your iOS app, with a link to the full documentation on that subject. Also, you can find in this Documentation how you can send notifications to multiple devices, in there you will also find an example of the code that you will need to use in the Cloud Function itself using the Admin SDK.

NOTE: Cloud Functions can be written in Node.js, Go, Java and Python, and all the sample codes for the Cloud functions are in those languages.

Ralemos
  • 5,571
  • 2
  • 9
  • 18
0

Here's how to send data from an iOS app to the RealTimeDatabase using Cloud Functions:

Before you start you need to have node.js/npm installed, it's easy to do, follow this youtube

1- assuming you have node installed, go to the Firebase console > select Functions in the right side > Upgrade > Pay as you go Blaze Plan > Get Started > Continue > Finish

2- open terminal and enter $ npm install -g firebase-tools (if this is already installed you can skip this step). If you get Error: EACCES: permission denied response then enter $ sudo npm install -g firebase-tools and enter your cpu password to continue. If you get an ERROR, newer versions of npm will want this instead $ sudo npm install --location=global firebase-tools

3- after it's finished enter $ npm --version to see which version you have installed (if you want the node version then use $ node -v)

4- create an empty folder and name it something like Cloud_Functions or whatever. Drag that folder into step 5 below. You will do all of the below steps inside this Cloud_Functions folder.

5- Go to the main folder where you Xcode project lives, mines is on my desktop and is named fooProject

6- cd into that folder $ cd fooProject then $ cd Cloud_Functions

7- in terminal enter $ pwd because if you have to be inside the Cloud_Functions folder for this wot work

8- in terminal enter $ firebase login (enter your login credentials and press enter)

9- assuming your in the correct folder, in terminal enter:

$ firebase init functions

10.A- You might see this option first (or not at all):

Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choices

Use your up/down key to choose Functions: Configure and deploy Cloud Functions, first press the SPACE BAR, second press ENTER. If you press Enter first then this won't work, you must press the Space Bar first and then Enter second. If this option doesn't appear as it didn't with one project, did with another project, then didn't again with another project, then 10.B below will definitely appear first.

10.B- You might see this option second (or first) Use an existing project, press enter

11- next option is Select a default Firebase project for this directory, use the up/down arrows to select your project, press enter

12- next option is What language would you like to use to write Cloud Functions?, the two options are Javascript and typescript, I used the up/down arrow to select Javascript then pressed enter

13- next option is Do you want to use ESLint to catch probable bugs and enforce style? I entered n and pressed enter. These rules are very strict and you will get a bunch of errors when you deploy in step 22 if you don't know the rules beforehand. If you aren't aware of them I suggest that you select n for no

14- next option is Do you want to install dependencies with npm now? I entered y and pressed enter

15- after it finished installing the next thing wasn't an option, it was a suggestion to update to the latest version and suggested entering $ npm install -g firebase-tools. I kept getting errors so I skipped this step because it's the same thing from step 2

16- while still inside the Cloud_Functions folder, run $ ls and you will see a functions folder. Run $ cd functions because the next step has to happen inside the functions folder.

17- $ pwd to make you are in the functions folder

18- next run $ npm i --save firebase-functions@latest

19- next run $ open index.js to open the index.js file. Mines automatically opened in Sublime

20- this is a simple youtube video on what to do with the existing code inside the index.js file. Or you can simply uncomment all of the code, make sure to press save (command+S), and then run $ firebase deploy --only functions. After a few minutes you should get a Deploy complete! statement

21- You can comment out the sample code and here is the code to receive some data from a view controller inside my iOS app (step 25). Make sure your Firebase rules via the console allow writes to whatever ref you are writing to and make sure billing is setup (step 1) because it verifies billing when you first deploy (step 22). Inside the index.js file enter:

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

exports.updateSneakerTypeToPostsRef = functions.https.onCall((data, context) => {

    const postId = data.postId; // this will be abc123
    const userId = data.uid; // this will be whatever the user's id is
    const sneakerName = data.sneakerName; // this will be Adidas
    const receivedTimeStamp = Date.now(); // Data.now() is how you receive the timestamp in Javascript/Node 

    // this is just a print statement
    console.log("received values =" + " | postId: " + postId + " | userId: " + userId + " | sneakerName: " + sneakerName + " | timeStamp: " + receivedTimeStamp);

    // this is the database path: posts/postId/userId in step 25 and I'm going to add the *sneakerName:Adidas* and *receivedTimeStamp* to it
    var postsRef = admin.database().ref('/posts/' + postId + '/' + userId);

    return postsRef.set({ "sneakerName": sneakerName, "timeStamp": receivedTimeStamp })
    .catch((error) => {
        console.log('ERROR - updateSneakerTypeToPostsRef() Failed: ', error);
    });
});

22- save the above file and now in terminal enter below, the part after the colon is the name of the exports. function from step 21 and it has to be the same exact name:

$ firebase deploy --only functions:updateSneakerTypeToPostsRef. This will only deploy this 1 function. If you have several functions then use $ firebase deploy --only functions to deploy them all at once

If you removed the initial sample code you will get a response: The following functions are found in your project but do not exist in your local source code: helloWorld(us-central...) If you are renaming a function or changing its region, it is recommended that you create the new function first before deleting the old one to prevent event loss. For more info, visit https://firebase.google.com/docs/functions/manage-functions#modify ? Would you like to proceed with deletion? Selecting no will continue the rest of the deployments.* You can enter no to delete it or yes to keep it, it doesn't matter. Either way press Enter afterwards.

This took about 3 minutes to finish but when it did I got functions[updateSneakerTypeToPostsRef(us-central4)]: Successful create operation. Deploy complete! I entered this below just for log info:

$ firebase functions:log // this is log data

Now to get to connect to the cloud function from inside the iOS app.

23- cd to the actual Xcode project, open your Podfile and put in pod 'Firebase/Functions' then install it $ pod install

24- after it's installed go whatever view controller and add import Firebase to the top of the file then add this line as a class property lazy var functions = Functions.functions()

25- Here is how to send data to the function inside the index.js file (step 21)

import Firebase
lazy var functions = Functions.functions()

@IBAction func buttonTapped(_sender : AnyObject){

    sendDataToCloudFunction()
}

func sendDataToCloudFunction() {

    let data: [String: Any] = ["postId": "abc123",
                               "uid": Auth.auth().currentUser!.uid,
                               "sneakerName": "Adidas"]

    let exportsName = "updateSneakerTypeToPostsRef" // *** this HAS TO BE the SAME exact function name from steps 21 and 22 ***

    functions.httpsCallable(exportsName).call(data) { (result, error) in
        print("Function returned")
        if let error = error as NSError? {
            if error.domain == FunctionsErrorDomain {
                let code = FunctionsErrorCode(rawValue: error.code)
                let message = error.localizedDescription
                let details = error.userInfo[FunctionsErrorDetailsKey]
                print(code.debugDescription)
                print(message.debugDescription)
                print(details.debugDescription)
            }
            print(error.localizedDescription)
            return
        }
            
        if let res = result {
            print("------->", res)
        }
            
        if let operationResult = (result?.data as? [String: Any])?["operationResult"] as? Int {
            print("\(operationResult)")
        }
    }
}

26- The result inside firebase will look like

@posts
   @abc123
      @whatever_the_userId_is...
         -sneakerName: "Adidas"
         -timeStamp: 1595874879.9619331
  1. This is a separate thing to do but you should also make sure that your App Engine and Cloud Function are in the same region. Read more here

  2. You can't print out your cloud functions via terminal but you can see them in the Google Cloud Console as stated in this answer

Lance Samaria
  • 17,576
  • 18
  • 108
  • 256