Navigation

PlateSpace Tutorial (iOS App)

This tutorial demonstrates how to use MongoDB Stitch to integrate the following services for an iOS application:

  • Facebook user authentication services
  • Anonymous authentication
  • MongoDB Service

PlateSpace is a sample native application built around a social, mobile, local restaurant search concept. The user will see a list of restaurants, sorted by their physical proximity to the user’s location. The user can browse through the restaurants, in either map view or a list view, and use advanced filtering and keywords to search for more entries.

Tapping on a restaurant leads to the restaurant’s page, which displays additional details including the average rating and reviews.

Logged in users will be able to add/edit ratings & reviews to the restaurant.

Prerequisites

  • Xcode Version 8.2.1
  • Familiarity with the Swift programming language.
  • Cosmos, a star rating control for iOS.
  • Yarn

Procedures

A. Create a MongoDB Stitch App

Estimated Time to Complete: ~8 minutes

1

Log into Atlas.

To use MongoDB Stitch, you must be logged into MongoDB Atlas. If you do not have an Atlas account, follow the instructions in the Atlas documentation to create an account.

2

Create an Atlas cluster.

If you do not already have an Atlas cluster for use with MongoDB Stitch, create a cluster.

Important

You must deploy the Atlas cluster with MongoDB version 3.4 or greater.

Atlas provides a Free Tier M0 replica set as well as paid M10+ clusters. Free Tier deployments have restrictions as compared to paid M10+ deployments but will work for the purposes of this tutorial. For complete documentation on these restrictions, see Atlas M0 (Free Tier) Limitations.

3

Add a MongoDB Stitch application.

  1. In Atlas, click Stitch Apps in the left-hand navigation.
  2. Click the Create New Application. The name can only contain ASCII letters, numbers, underscores, and hyphens.
  3. Select your Atlas cluster for your MongoDB service.
  4. Click Create.

Upon creation of your app, you will be redirected to the MongoDB Stitch console. Your app is created with a MongoDB service named mongodb-atlas.

B. Define Pipelines

Estimated Time to Complete: ~10 minutes

1

Define a pipeline named geoNear.

Define a pipeline that reads from the platespace.restaurants collection to find the nearest restaurants given the input arguments.

  1. Click Pipelines and then click New Pipeline.

  2. Enter the following properties for the pipeline:

    Name geoNear
    Private Leave unselected. If selected, the pipeline is inaccessible by a client and is only accessible in other MongoDB Stitch definitions, such as webhooks and other pipelines.
    Skip Rules Leave unselected. If selected, rules do not apply to the service actions in the pipeline stages.
    Can Evaluate Leave as {}. The Can Evaluate condition determines whether the pipeline can be run. An empty document {} always evaluates to true, indicating that the pipeline can always be run.
    Parameters

    Add the following parameters:

    • longitude
    • latitude
    • query
    • limit
    • minDistance

    Select all except the query parameter as Required. Clients calling the pipeline must include the required parameters.

    Note

    In the pipeline stage, you bind parameters to stage-defined variables to reference them in the stage. See the stage edit step below for details.

  3. For the output type, select Array

  4. For the displayed stage, edit the following:

    Service Select mongodb-atlas
    Action

    Select aggregate

    In the text box that appears, enter the following aggregation:

    {
      "database": "platespace",
      "collection": "restaurants",
      "pipeline": [
        {
          "$geoNear": {
            "near": {
              "coordinates": [
                "%%vars.longitude",
                "%%vars.latitude"
              ],
              "type": "Point"
            },
            "query": "%%vars.query",
            "limit": "%%vars.limit",
            "minDistance": "%%vars.minDistance",
            "distanceField": "dist",
            "spherical": true
          }
        }
      ]
    }
    
    Bind data to %%vars

    You cannot directly reference the parameters in the stage. Instead, define variables that reference the parameters, and reference the variables in the stage.

    Enable Bind data to %%vars and copy the following in the Bind data to %%vars text box:

    {
      "longitude": "%%args.longitude",
      "latitude": "%%args.latitude",
      "query": "%%args.query",
      "limit": "%%args.limit",
      "minDistance": "%%args.minDistance"
    }
    

    Click Done

  5. Click Save.

See Named Pipelines for more information.

2

Define a pipeline named aggregateRestaurant.

Define a pipeline that calculates the number of reviews and the average rating for a specified restaurant.

  1. Click Pipelines and then click New Pipeline.

  2. Enter the following properties for the pipeline.

    Name aggregateRestaurant
    Private Leave unselected. If selected, the pipeline is inaccessible by a client and is only accessible in other MongoDB Stitch definitions, such as webhooks and other pipelines.
    Skip Rules Leave unselected. If selected, rules do not apply to the service actions in the pipeline stages.
    Can Evaluate Leave as {}. The Can Evaluate condition determines whether the pipeline can be run. An empty document {} always evaluates to true, indicating that the pipeline can always be run.
    Parameters

    Add the following parameter:

    • restaurantId

    Set the parameter as required. Clients calling the pipeline must include this parameter.

    Note

    In the pipeline stage, you bind parameters to stage-defined variables to reference them in the stage. See the edit stage step below for details.

  3. For the output type, ensure that Single Document is selected.

  4. For the displayed stage, edit the following:

    Service Select mongodb-atlas.
    Action

    Select aggregate.

    In the text box that appears, enter the following aggregation:

    {
      "database": "platespace",
      "collection": "reviewsRatings",
      "pipeline": [
        {
          "$match": {
            "restaurantId": "%%vars.restaurantId",
            "rate": {
              "$exists": true
            }
          }
        },
        {
          "$group": {
            "_id": "$restaurantId",
            "average": {
              "$avg": "$rate"
            },
            "count": {
              "$sum": 1
            }
          }
        }
      ]
    }
    
    Bind data to %%vars

    You cannot directly reference the parameter restaurantId in the stage. Instead, define a variable that references the parameter, and then reference the variable in the stage.

    Enable Bind data to %%vars and copy the following in the Bind data to %%vars text box:

    {
      "restaurantId": "%%args.restaurantId"
    }
    

    Click Done.

  5. Click Save.

See Named Pipelines for more information.

3

Define a pipeline named updateRatings.

Define a pipeline that updates the platespace.restaurants collection with the number of reviews and the average rating for a specified restaurant.

  1. Click Pipelines and then click New Pipeline.

  2. Enter the following properties for the pipeline.

    Name updateRatings
    Private Leave unselected. If selected, the pipeline is inaccessible by a client and is only accessible in other MongoDB Stitch definitions, such as webhooks and other pipelines.
    Skip Rules Enable skip rules. In a later step, you will set up write rules that prevents write operations to this collection. As such, in order for the pipeline to bypass this rule and write to the collection, you need to skip rules.
    Can Evaluate Leave as {}. The Can Evaluate condition determines whether the pipeline can be run. An empty document {} always evaluates to true, indicating that the pipeline can always be run.
    Parameters

    Add the following parameter:

    • restaurantId

    Select the parameter as required. Clients calling the pipeline must include this parameter.

    Note

    In the pipeline stage, you bind parameters to stage-defined variables to reference them in the stage. See the edit stage step below for details.

  3. For the output type, ensure that Single Document is selected.

  4. For the displayed stage, edit the following:

    Service Select mongodb-atlas.
    Action

    Select update.

    In the text box that appears, enter the following aggregation:

    {
      "database": "platespace",
      "collection": "restaurants",
      "query": {
        "_id": "%%vars.restaurantId"
      },
      "update": {
        "$set": {
          "averageRating": "%%vars.pipelineResult.average",
          "numberOfRates": "%%vars.pipelineResult.count"
        }
      },
      "upsert": false,
      "multi": false
    }
    
    Bind data to %%vars

    You cannot directly reference the parameter restaurantId in the stage. Instead, define a variable that references the parameter, and then reference the variable in the stage. You can also reference other named pipelines using the %pipeline expansion.

    Enable Bind data to %%vars and copy the following in the Bind data to %%vars text box:

    {
      "restaurantId": "%%args.restaurantId",
      "pipelineResult": {
        "%pipeline": {
          "name": "aggregateRestaurant",
          "args": {
            "restaurantId": "%%args.restaurantId"
          }
        }
      }
    }
    

    Click Done.

  5. Save.

See Named Pipelines for more information.

C. Define Rules

Estimated Time to Complete: ~6 minutes

1

Add rules for each collection the MongoDB service must access.

  1. Click on the MongoDB service mongodb-atlas.

  2. In the Rules tab, add the following namespaces:

    1. Database: platespace

      Collection: restaurants

    2. Database: platespace

      Collection: reviewsRatings

Note

You must add rules for each collection the MongoDB service accesses.

See MongoDB Service Rules for more information.

2

Review the rules for platespace.restaurants collection.

Click on platespace.restaurants to view the read rule, write rule, the validation rule, and the filter rule.

MongoDB Authorization

MongoDB Stitch rules do not override the read and write access (i.e. authorization) that may have been set up separately in MongoDB. That is, MongoDB Stitch rules determine whether the fields are readable or writable; not whether the client has authorization to read or write to a particular database or collection.

Similarly, MongoDB Stitch validation rules do not override document validation rules set up separately in MongoDB.

  1. Review the read rule for the top-level document of platespace.restaurants.

    Modify the read rule for the top-level document to the following and click Save:

    { }
    

    The new rule specifies that all the fields in the documents are always readable. For more information on MongoDB read rules, see MongoDB Service Read Rule.

  2. Review the write rule for the top-level document of platespace.restaurants.

    Modify the write rule for the top-level document to the following and click Save:

    {
       "%%true": false
    }
    

    The new rule specifies that no fields are writable . For more information on MongoDB write rules, see MongoDB Service Write Rule.

  3. Delete the owner_id field.

    For this collection, the owner_id field does not exist. Delete the owner_id field by hovering over it and clicking the x on the right-hand side.

  4. Allow all other fields for platespace.restaurants.

    By default, Allow All Other Fields flag is enabled so that any unlisted fields are readable/writeable as long as the document meets the read/write rules. Alternatively, you can explicitly define all the fields for this collection and disable Allow All Other Fields to control the shape of the documents.

  5. Review filters for platespace.restaurants.

    1. Click on the Filters.

    2. Delete the default filter.

      For more information on filters, see MongoDB Service Filters.

  6. Save your changes.

3

Review the rules for platespace.reviewsRatings collection.

Click on platespace.reviewsRatings to view the read rule, write rule, the validation rule, and the filter rule.

MongoDB Authorization

MongoDB Stitch rules do not override the read and write access (i.e. authorization) that may have been set up separately in MongoDB. That is, MongoDB Stitch rules determine whether the fields are readable or writable; not whether the client has authorization to read or write to a particular database or collection.

Similarly, MongoDB Stitch validation rules do not override document validation rules set up separately in MongoDB.

  1. Review the read rule for the top-level document of platespace.reviewsRatings.

    Modify the read rule for the top-level document to the following and click Save:

    { }
    

    The new rule specifies that all the fields in the documents are always readable. For more information on MongoDB read rules, see MongoDB Service Read Rule.

  2. Review the write rule for the top-level document of platespace.reviewsRatings.

    Leave the rule as is.

    {
       "owner_id": "%%user.id"
    }
    

    The rule specifies that all fields in the documents are writable if the id of the current user matches the owner_id field of the document. For more information on MongoDB write rules, see MongoDB Service Write Rule.

  3. Review the validation rule on the owner_id field of platespace.reviewsRatings. To view the rule, click on the owner_id field.

    {
      "%or": [
        { "%%prev": "%%user.id" },
        { "%%prev": { "%exists": false } }
      ]
    }
    

    The rule specifies that a write operation is valid if either of the following conditions are true:

    • The operation modifies a document where the value of the owner_id field matches the %%user.id.
    • The operation modifies a document that previously did not have an owner_id field, such as inserting a new document.

    For more information on MongoDB validation rules, see MongoDB Service Validation.

  4. Allow all other fields.

    By default, Allow All Other Fields flag is enabled so that any unlisted fields are readable/writeable as long as the document meets the read/write rules. Alternatively, you can explicitly define all the fields for this collection and disable Allow All Other Fields to control the shape of the documents.

  5. Review the filters for platespace.reviewsRatings.

    1. Click on Filters.

      By default, the collection has the following filter rule:

      When Match Expression
      { "%%true": true } { "owner_id": "%%user.id" }

      This filter indicates that when %%true equals true (i.e. always), apply the following filter { "owner_id": "%%user.id" } to the read and write operations. This filter is in addition to the query predicates for read and write operations.

    2. Delete the default filter.

      For more information on filters, see MongoDB Service Filters.

  6. Save your changes.

D. Set Up Facebook Auth

Estimated Time to Complete: ~7 minutes

1

Set up an app in Facebook.

  1. Log in to your Facebook Developer account. If you do not have a Facebook developer account or even a Facebook account, see Facebook - Register and Configure an App.

  2. Add a new Facebook application.

  3. Once created, your application view opens to Product Setup. If not, go to your newly added app and click Add Product.

  4. In Product Setup, for Facebook Login, click Set Up. This brings up the Quickstart for Facebook Login.

  5. In the Quickstart for iOS:

    1. You can skip the Download the Facebook SDK for iOS step and the Add Login Kit to your Xcode Project step as the PlateSpace iOS app project covers these steps.

    2. For Add your Bundle Identifier, enter:

      com.mongodb.PlateSpace
      
    3. Optional. You can enable/disable Single Sign On and continue to the next step.

    4. Note the following FacebookAppID and CFBundleURLSchemes entries for your project file:

      <array>
        <dict>
        <key>CFBundleURLSchemes</key>
        <array>
          <string>fb111111111111111</string>
        </array>
        </dict>
      </array>
      <key>FacebookAppID</key>
      <string>111111111111111</string>
      

      You will use these entries later during the setup step of the Xcode project in this tutorial.

    5. You can omit the remaining QuickStart steps.

  6. Click Settings under Facebook Login in the left-hand navigation menu.

  7. Under Valid OAuth redirect URIs, add the following entry:

    https://stitch.mongodb.com/api/client/v1.0/auth/callback
    
  8. Save changes.

2

Retrieve the App ID and App Secret for your Facebook app.

  1. Click Settings for your Facebook application (i.e. not the Settings under Facebook Login).
  2. Note the App ID and App Secret. You will use the information in the Authentication section of the MongoDB Stitch Admin Console.
3

Configure MongoDB Stitch for Facebook Authentication.

  1. Click Authentication in the left side navigation in the MongoDB Stitch console.

    The Authentication page displays the Authentication Providers information.

  2. For Facebook, click the Edit button.

  3. In the Edit Provider dialog,

    • Switch Facebook to enabled.
    • Enter your new Facebook App ID in the Client ID field and Facebook App Secret in Client Secret field.
  4. Click Save.

E. Set Up Anonymous Auth

Estimated Time to Complete: ~2 minutes

1

Click Authentication in the left side navigation in the MongoDB Stitch console.

The Authentication page displays the Authentication Providers information.

2

Enable Anonymous Authentication.

  1. For Allow users to log in anonymously, click the Edit button.
  2. Switch Allow users to log in anonymously to enabled.
  3. Close the dialog.

F. Download the PlateSpace Application Source

Estimated Time to Complete: ~2 minutes

Download from the MongoDB Stitch GitHub example repository and unzip. The source code for the PlateSpace application can be found in the PlateSpace directory.

G. Populate the restaurants Collection from Yelp.

Estimated Time to Complete: ~8 minutes

1

Create a new Yelp app.

  1. Log in to your Yelp developer account.
  2. Create a new Yelp app.
2

Get an OAuth token for your Yelp app.

To get your auth token, issue the following POST operation, substituting <CLIENT_ID> with your client id and <CLIENT_SECRET> with your client secret:

curl -X POST https://api.yelp.com/oauth2/token -d 'grant_type=client_credentials&client_id=<CLIENT_ID>&client_secret=<CLIENT_SECRET>'

For more information, see https://www.yelp.com/developers/documentation/v3/authentication

3

Edit the config.js file with your Yelp token.

  1. From the downloaded examples directory, navigate to the PlateSpace/ETL folder.

  2. Edit the config.js file:

    For YELP_AUTH_TOKEN, enter "Bearer <YOUR_YELP_TOKEN>" where <YOUR_YELP_TOKEN> is the auth token for your app.

4

Edit the config.js file with your Atlas cluster connection info.

  1. Go to Atlas, and click on Clusters.

  2. For your cluster linked to the MongoDB Stitch app, click Connect to view the Connect dialog.

  3. Click Connect your application and copy the URI Connection string.

  4. Edit the config.js file:

    For MONGODB_ATLAS_URI, enter the connection string to your Atlas cluster.

    Substitute <PASSWORD> for your password and test database with the target database platespace.

5

Add IP Address to your Atlas cluster’s IP Whitelist.

Add to your Atlas cluster’s IP Whitelist the IP address from which you will run the load script.

  1. From the Connect dialog (see previous step), you can add to the IP Whistelist.
  2. Add the IP address from which you will run the load script.
6

Load data into restaurants collection.

  1. Install dependencies for the script.

    yarn
    
  2. Run the load script to populate the platespace.restaurants collection:

    node index.js
    

If you wish to delete the collection, you can run node clearRestCollection.js.

H. Set Up and Run PlateSpace App

Estimated Time to Complete: ~8 minutes

1

Open the project in Xcode.

  1. Navigate to the downloaded example directory and navigate to the /PlateSpace/iOS directory.
  2. Open the workspace file.
2

Update Xcode project with your MongoDB Stitch App ID.

From the MongoDBSample view:

  1. Open the MongoDBSample/Resources/PlateSpace-Info.plist file.

  2. Update the APP_ID with your MongoDB Stitch app id and save.

    In the MongoDB Stitch console, you can find your App ID in the Clients view.

3

Update project with your Facebook App ID.

  1. Open the PlateSpace/Resources/Info.plist file.
  2. Update the FacebookAppID with your facebook_app_id entry. The entry is listed in the step Configure Your info.plist in the Facebook iOS Login Quickstart.
  3. Click on the PlateSpace project (not the folder) in the left hand navigation pane.
  4. Under Targets, ensure that PlateSpace is selected.
  5. Click on the Info tab and expand the URL Types.
  6. For YOUR_FACEBOOK_URL_SCHEME, enter the CFBundleURLSchemes value from the Configure Your info.plist step. The value has the form: fb<facebook_app_id>.
4

Review code to connect to MongoDB Stitch.

Use the StitchClient class to connect to your MongoDB Stitch application.

The PlateSpace/Classes/Managers/MongoDBManager.swift file has the following code:

import StitchCore
import MongoDB

//...

class MongoDBManager {
    let stitchClient: StitchClient
    /// ...
    init() {

        /// Init a Stitch client
        stitchClient = StitchClient(appId: MongoDBManager.appId)

        /// ...
    }
    /// ...
}
5

Review code to authenticate application users.

StitchClient provides various functions to authenticate:

The PlateSpace/Classes/Controllers/LoginViewController.swift file contains the following code:

import FBSDKLoginKit
import MongoCore

//...

// Anonymous authentication

MongoDBManager.shared.stitchClient.anonymousAuth().response { [weak self] (result) in
   //...
}

//...

// Facebook authentication
   FBSDKLoginManager().logIn(withReadPermissions: ["email"], from: self) { [weak self]
       (loginResult: FBSDKLoginManagerLoginResult?, error: Error?) in
       if let accessToken = FBSDKAccessToken.current() {
           self?.login(withAccessToken: accessToken)
       } else {
            ///...
       }
    }

/// Login with FB token
private func login(withAccessToken accessToken: FBSDKAccessToken) {

   /// Create provider using the FB token
   let provider = FacebookAuthProvider(accessToken: accessToken.tokenString)

   /// Login with this provider
   login(withProvider: provider)
}

/// Login with provider
private func login(withProvider provider: AuthProvider) {
    MongoDBManager.shared.stitchClient.login(withProvider: provider).response { [weak self] (result) in
6

Review code to query a collection.

To query a collection, build a Query using a Criteria. For example, the PlateSpace/Classes/Controllers/SingleRestaurantViewController/ SingleRestaurantViewController.swift file contains the following code to query the restaurants collection by the restaurant id:

func fetchRestaurant() {

    /// Fetch an updated instance of this restaurant with updated values
    guard let id = restaurant?.objectId else {return}

    let criteria: Criteria = .equals(field: "_id", value: id)

    let query = Query<Restaurant>(criteria: criteria, mongoDBClient: MongoDBManager.shared.mongoClient)

    query.find().response(completionHandler: { /// ... })
}
7

Review code to excute pipelines.

To execute pipelines, use StitchClient.executePipeline().

For example, the PlateSpace/Classes/Managers/ReviewsFlowManager.swift contains the following code to execute the pipeline named updateRatings:

static let pipelineName        = "updateRatings"

/// ...

private func updateRating(withRestaurantId id: ObjectId) {
     /// Build a custom pipeline to update the restaurant review model

     /// Pipeline args item specify the structure in which the query results will be returned
     let argsItems: BsonArray = [Document(dictionary: [Consts.pipelineResultKey : "%%vars.\(Consts.pipelineCommandName)"])]

     /// Arguments for the pipeline
     let args = Document(dictionary: [Consts.argsRestaurantIdKey : id])

     /// The document which is passed to the 'let' field
     let letDocument = Document(dictionary: ["%pipeline" : Document(dictionary: [
         "name" : Consts.pipelineName,
         "args" : args
         ])])

     /// Build the pipeline
     let pipeline = Pipeline (
         action: "literal",
         args: ["items" : argsItems],
         `let`: Document(dictionary: [Consts.pipelineCommandName :  letDocument])
     )

     /// Execute the pipeline
     MongoDBManager.shared.stitchClient.executePipeline(pipeline: pipeline).response { /// ...
     }
}
8

Run the application.

Collection Information

Each document in the platespace.restaurants collection has the form:

{
   "_id" : <ObjectId>,
   "name" : <String>,
   "address" : <String>,
   "phone" : <String>,
   "Image_url" : <String>,
   "website" : <String>,
   "averageRating" : <Double>,
   "numberOfRates" : <Double>,
   "openingHours" :  { "end" : <String>, "start" : <String> },
   "attributes" : {
      "veganFriendly" : <Boolean>,
      "openOnWeekends" : <Boolean>,
      "hasParking" : <Boolean>,
      "hasWifi" : <Boolean>
   },
   "location" : {
      "coordinates" : [ "longitude" : <Double>, "latitude" : <Double> ],
      "type" : "Point"
   }
}

Note

openingHours and attributes are custom fields added by our sample script to load data and not from Yelp. The field values are generated with dummy data.

Each document in the platespace.reviewsRatings collection has the form:

{
   "_id" : <ObjectId>,
   "owner_id" : <string>,
   "restaurantId" : <ObjectId>,
   "nameOfCommenter" : <String>,
   "comment": <String>,
   "rate" : <Integer>,
   "dateOfComment" : <Date>
}