Tag Archive for mobile

Goodbye NSPredicate, hello Realm Swift Query API

Introduction

I’m not a fan of writing code using pseudo-English text strings. It’s a major context switch when you’ve been writing “native” code. Compilers don’t detect errors in the strings, whether syntax errors or mismatched types, leaving you to learn of your mistakes when your app crashes.

I spent more than seven years working at MySQL and Oracle, and still wasn’t comfortable writing anything but the simplest of SQL queries. I left to join MongoDB because I knew that the object/document model was the way that developers should work with their data. I also knew that idiomatic queries for each programming language were the way to go.

That’s why I was really excited when MongoDB acquired Realm—a leading mobile object database. You work with Realm objects in your native language (in this case, Swift) to manipulate your data.

However, there was one area that felt odd in Realm’s Swift SDK. You had to use NSPredicate when searching for Realm objects that match your criteria. NSPredicates are strings with variable substitution. 🤦‍♂️

NSPredicates are used when searching for data in Apple’s Core Data database, and so it was a reasonable design decision. It meant that iOS developers could reuse the skills they’d developed while working with Core Data.

But, I hate writing code as strings.

The good news is that the Realm SDK for Swift has added the option to use type-safe queries through the Realm Swift Query API. 🥳.

You now have the option whether to filter using NSPredicates:

let predicate = NSPredicate(format: "isSoft == %@", NSNumber(value: wantSoft)
let decisions = unfilteredDecisions.filter(predicate)

or with the new Realm Swift Query API:

let decisions = unfilteredDecisions.where { $0.isSoft == wantSoft }

In this article, I’m going to show you some examples of how to use the Realm Swift Query API. I’ll also show you an example where wrangling with NSPredicate strings has frustrated me.

Prerequisites

Using The Realm Swift Query API

I have a number of existing Realm iOS apps using NSPredicates. When I learnt of the new query API, the first thing I wanted to do was try to replace some of “legacy” queries. I’ll start by describing that experience, and then show what other type-safe queries are possible.

Replacing an NSPredicate

I’ll start with the example I gave in the introduction (and how the NSPredicate version had previously frustrated me).

I have an app to train you on what decisions to make in Black Jack (based on the cards you’ve been dealt and the card that the dealer is showing). There are three different decision matrices based on the cards you’ve been dealt:

  • Whether you have the option to split your hand (you’ve been dealt two cards with the same value)
  • Your hand is “soft” (you’ve been dealt an ace, which can take the value of either one or eleven)
  • Any other hand

All of the decision-data for the app is held in Decisions objects:

class Decisions: Object, ObjectKeyIdentifiable {
   @Persisted var decisions = List<DecisionList>()
   @Persisted var isSoft = false
   @Persisted var isSplit = false
   ...
}

SoftDecisionView needs to find the Decisions object where isSoft is set to true. That requires a simple NSPredicate:

struct SoftDecisionView: View {
   @ObservedResults(Decisions.self, filter: NSPredicate(format: "isSoft == YES")) var decisions
   ...
}

But, what if I’d mistyped the attribute name? There’s no Xcode auto-complete to help when writing code within a string, and this code builds with no errors or warnings:

struct SoftDecisionView: View {
   @ObservedResults(Decisions.self, filter: NSPredicate(format: "issoft == YES")) var decisions
   ...
}

When I run the code, it works initially. But, when I’m dealt a soft hand, I get this runtime crash:

Terminating app due to uncaught exception 'Invalid property name', reason: 'Property 'issoft' not found in object of type 'Decisions''

Rather than having a dedicated view for each of the three types of hand, I want to experiment with having a single view to handle all three.

SwiftUI doesn’t allow me to use variables (or even named constants) as part of the filter criteria for @ObservedResults. This is because the struct hasn’t been initialized until after the @ObservedResults is defined. To live within SwitfUIs constraints, the filtering is moved into the view’s body:

struct SoftDecisionView: View {
   @ObservedResults(Decisions.self) var unfilteredDecisions
   let isSoft = true

   var body: some View {
       let predicate = NSPredicate(format: "isSoft == %@", isSoft)
       let decisions = unfilteredDecisions.filter(predicate)
   ...
}

Again, this builds, but the app crashes as soon as I’m dealt a soft hand. This time, the error is much more cryptic:

Thread 1: EXC_BAD_ACCESS (code=1, address=0x1)

It turns out that, you need to convert the boolean value to an NSNumber before substituting it into the NSPredicate string:

struct SoftDecisionView: View {
   @ObservedResults(Decisions.self) var unfilteredDecisions


   let isSoft = true


   var body: some View {
       let predicate = NSPredicate(format: "isSoft == %@", NSNumber(value: isSoft))
       let decisions = unfilteredDecisions.filter(predicate)
   ...
}

Who knew? OK, StackOverflow did, but it took me quite a while to find the solution.

Hopefully, this gives you a feeling for why I don’t like writing strings in place of code.

This is the same code using the new (type-safe) Realm Swift Query API:

struct SoftDecisionView: View {
   @ObservedResults(Decisions.self) var unfilteredDecisions
   let isSoft = true

   var body: some View {
       let decisions = unfilteredDecisions.where { $0.isSoft == isSoft }
   ...
}

The code’s simpler, and (even better) Xcode won’t let me use the wrong field name or type—giving me this error before I even try running the code:

Xcode showing the error "Binary operator '==' cannot be applied to operands of type 'Query<Boo>‘ and ‘Int'” title=”Xcode showing the error Binary operator ‘==’ cannot be applied to operands of type ‘Query<Bool>‘ and ‘Int'” /></p>
<h3>Experimenting With Other Sample Queries</h3>
<p>In my <a href=RCurrency app, I was able to replace this NSPredicate-based code:

struct CurrencyRowContainerView: View {
   @ObservedResults(Rate.self) var rates
   let baseSymbol: String
   let symbol: String

   var rate: Rate? {
       NSPredicate(format: "query.from = %@ AND query.to = %@", baseSymbol, symbol)).first
   }
   ...
}

With this:

struct CurrencyRowContainerView: View {
   @ObservedResults(Rate.self) var rates
   let baseSymbol: String
   let symbol: String

   var rate: Rate? {
       rates.where { $0.query.from == baseSymbol && $0.query.to == symbol }.first
   }
   ...
}

Again, I find this more Swift-like, and bugs will get caught as I type/build rather than when the app crashes.

I’ll use this simple Task Object to show a few more example queries:

class Task: Object, ObjectKeyIdentifiable {
   @Persisted var name = ""
   @Persisted var isComplete = false
   @Persisted var assignee: String?
   @Persisted var priority = 0
   @Persisted var progressMinutes = 0
}

All in-progress tasks assigned to name:

let myStartedTasks = realm.objects(Task.self).where {
   ($0.progressMinutes > 0) && ($0.assignee == name)
}

All tasks where the priority is higher than minPriority:

let highPriorityTasks = realm.objects(Task.self).where {
   $0.priority >= minPriority
}

All tasks that have a priority that’s an integer between -1 and minPriority:

let lowPriorityTasks = realm.objects(Task.self).where {
   $0.priority.contains(-1...minPriority)
}

All tasks where the assignee name string includes namePart:

let tasksForName = realm.objects(Task.self).where {
   $0.assignee.contains(namePart)
}

Filtering on Sub-Objects

You may need to filter your Realm objects on values within their sub-objects. Those sub-object may be EmbeddedObjects or part of a List.

I’ll use the Project class to illustrate filtering on the attributes of sub-documents:

class Project: Object, ObjectKeyIdentifiable {
   @Persisted var name = ""
   @Persisted var tasks: List<Task>
}

All projects that include a task that’s in-progress, and is assigned to a given user:

let myActiveProjects = realm.objects(Project.self).where {
   ($0.tasks.progressMinutes >= 1) && ($0.tasks.assignee == name)
}

Including the Query When Creating the Original Results (SwiftUI)

At the time of writing, this feature wasn’t released, but it can be tested using this PR.

You can include the where modifier directly in your @ObservedResults call. That avoids the need to refine your results inside your view’s body:

@ObservedResults(Decisions.self, where: { $0.isSoft == true }) var decisions

Unfortunately, SwiftUI rules still mean that you can’t use variables or named constants in your where block for @ObservedResults.

Conclusion

Realm type-safe queries provide a simple, idiomatic way to filter results in Swift. If you have a bug in your query, it should be caught by Xcode rather than at run-time.

You can find more information in the docs. If you want to see hundreds of examples, and how they map to equivalent NSPredicate queries, then take a look at the test cases.

For those that prefer working with NSPredicates, you can continue to do so. In fact, the Realm Swift Query API runs on top of the NSPredicate functionality, so they’re not going anywhere soon.

Please provide feedback and ask any questions in the Realm Community Forum.





Using Maps and Location Data in Your SwiftUI (+Realm) App

Introduction

Embedding Apple Maps and location functionality in SwiftUI apps used to be a bit of a pain. It required writing your own SwiftUI wrapper around UIKit code—see these examples from the O-FISH app:

If you only need to support iOS14 and later, then you can forget most of that messy code 😊. If you need to support iOS13—sorry, you need to go the O-FISH route!

iOS14 introduced the Map SwiftUI view (part of Mapkit) allowing you to embed maps directly into your SwiftUI apps without messy wrapper code.

This article shows you how to embed Apple Maps into your app views using Mapkit’s Map view. We’ll then look at how you can fetch the user’s current location—with their permission, of course!

Finally, we’ll see how to store the location data in Realm in a format that lets MongoDB Realm sync it to MongoDB Atlas. Once in Atlas, you can add a geospatial index and use MongoDB Charts to plot the data on a map—we’ll look at that too.

Most of the code snippets have been extracted from the RChat app. That app is a good place to see maps and location data in action. Building a Mobile Chat App Using Realm – The New and Easier Way is a good place to learn more about the RChat app—including how to enable MongoDB Realm Sync.

Prerequisites

  • Realm-Cocoa 10.8.0+ (may work with some 10.7.X versions)
  • iOS 14.5+ (Mapkit was introduced in iOS 14.0 and so most features should work with earlier iOS 14.X versions)
  • XCode12+

How to Add an Apple Map to Your SwiftUI App

To begin, let’s create a simple view that displays a map, the coordinates of the center of that map, and the zoom level:

Gif of scrolling around an embedded Apple Map and seeing the reported coordinates changing

With Mapkit and SwiftUI, this only takes a few lines of code:

import MapKit
import SwiftUI

struct MyMapView: View {
    @State private var region: MKCoordinateRegion = MKCoordinateRegion(
        center: CLLocationCoordinate2D(latitude: MapDefaults.latitude, longitude: MapDefaults.longitude),
        span: MKCoordinateSpan(latitudeDelta: MapDefaults.zoom, longitudeDelta: MapDefaults.zoom))

    private enum MapDefaults {
        static let latitude = 45.872
        static let longitude = -1.248
        static let zoom = 0.5
    }

    var body: some View {
        VStack {
            Text("lat: \(region.center.latitude), long: \(region.center.longitude). Zoom: \(region.span.latitudeDelta)")
            .font(.caption)
            .padding()
            Map(coordinateRegion: $region,
                interactionModes: .all,
                showsUserLocation: true)
        }
    }
}

Note that showsUserLocation won’t work unless the user has already given the app permission to use their location—we’ll get to that.

region is initialized to a starting location, but it’s updated by the Map view as the user scrolls and zooms in and out.

Adding Bells and Whistles to Your Maps (Pins at Least)

Pins can be added to a map in the form of “annotations.” Let’s start with a single pin:

Embedded Apple Map showing a red pin

Annotations are provided as an array of structs where each instance must contain the coordinates of the pin. The struct must also conform to the Identifiable protocol:

struct MyAnnotationItem: Identifiable {
    var coordinate: CLLocationCoordinate2D
    let id = UUID()
}

We can now create an array of MyAnnotationItem structs:

let annotationItems = [
    MyAnnotationItem(coordinate: CLLocationCoordinate2D(
        latitude: MapDefaults.latitude,
        longitude: MapDefaults.longitude))]

We then pass annotationItems to the MapView and indicate that we want a MapMarker at the contained coordinates:

Map(coordinateRegion: $region,
    interactionModes: .all,
    showsUserLocation: true,
    annotationItems: annotationItems) { item in
        MapMarker(coordinate: item.coordinate)
    }

That gives us the result we wanted.

What if we want multiple pins? Not a problem. Just add more MyAnnotationItem instances to the array.

All of the pins will be the same default color. But, what if we want different colored pins? It’s simple to extend our code to produce this:

Embedded Apple Map showing red, yellow, and plue pins at different locations

Firstly, we need to extend MyAnnotationItem to include an optional color and a tint that returns color if it’s been defined and “red” if not:

struct MyAnnotationItem: Identifiable {
    var coordinate: CLLocationCoordinate2D
    var color: Color?
    var tint: Color { color ?? .red }
    let id = UUID()
}

In our sample data, we can now choose to provide a color for each annotation:

let annotationItems = [
    MyAnnotationItem(
        coordinate: CLLocationCoordinate2D(
            latitude: MapDefaults.latitude,
            longitude: MapDefaults.longitude)),
    MyAnnotationItem(
        coordinate: CLLocationCoordinate2D(
            latitude: 45.8827419,
            longitude: -1.1932383),
        color: .yellow),
    MyAnnotationItem(
        coordinate: CLLocationCoordinate2D(
            latitude: 45.915737,
            longitude: -1.3300991),
        color: .blue)
]

The MapView can then use the tint:

Map(coordinateRegion: $region,
    interactionModes: .all,
    showsUserLocation: true,
    annotationItems: annotationItems) { item in
    MapMarker(
        coordinate: item.coordinate,
        tint: item.tint)
}

If you get bored of pins, you can use MapAnnotation to use any view you like for your annotations:

Map(coordinateRegion: $region,
    interactionModes: .all,
    showsUserLocation: true,
    annotationItems: annotationItems) { item in
    MapAnnotation(coordinate: item.coordinate) {
        Image(systemName: "gamecontroller.fill")
            .foregroundColor(item.tint)
    }
}

This is the result:

Apple Map showing red, yellow and blue game controller icons at different locations on the map

You could also include the name of the system image to use with each annotation.

This gist contains the final code for the view.

Finding Your User’s Location

Asking for Permission

Apple is pretty vocal about respecting the privacy of their users, and so it shouldn’t be a shock that your app will have to request permission before being able to access a user’s location.

The first step is to add a key-value pair to your Xcode project to indicate that the app may request permission to access the user’s location, and what text should be displayed in the alert. You can add the pair to the “Info.plist” file:

Privacy - Location When In Use Usage Description : We'll only use your location when you ask to include it in a message

Screenshot from Xcode showing the key-value pair for requesting permission for the app to access the user's location

Once that setting has been added, the user should see an alert the first time that the app attempts to access their current location:

iPhone screenshot – app is requesting permission to access the user's location

Accessing Current Location

While Mapkit has made maps simple and native in SwiftUI, the same can’t be said for location data.

You need to create a SwiftUI wrapper for Apple’s Core Location functionality. There’s not a lot of value in explaining this boilerplate code—just copy this code from RChat’s LocationHelper.swift file, and paste it into your app:

import CoreLocation

class LocationHelper: NSObject, ObservableObject {

    static let shared = LocationHelper()
    static let DefaultLocation = CLLocationCoordinate2D(latitude: 45.8827419, longitude: -1.1932383)

    static var currentLocation: CLLocationCoordinate2D {
        guard let location = shared.locationManager.location else {
            return DefaultLocation
        }
        return location.coordinate
    }

    private let locationManager = CLLocationManager()

    private override init() {
        super.init()
        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.requestWhenInUseAuthorization()
        locationManager.startUpdatingLocation()
    }
}

extension LocationHelper: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { }

    public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print("Location manager failed with error: \(error.localizedDescription)")
    }

    public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        print("Location manager changed the status: \(status)")
    }
}

Once added, you can access the user’s location with this simple call:

let location = LocationHelper.currentLocation

Store Location Data in Your Realm Database

The Location Format Expected by MongoDB

Realm doesn’t have a native type for a geographic location, and so it’s up to us how we choose to store it in a Realm Object. That is, unless we want to synchronize the data to MongoDB Atlas using MongoDB Realm Sync, and go on to use MongoDB’s geospatial functionality.

To make the best use of the location data in Atlas, we need to add a geospatial index to the field (which we’ll see how to do soon.) That means storing the location in a supported format. Not all options will work with Realm Sync (e.g., it’s not guaranteed that attributes will appear in the same order in your Realm Object and the synced Atlas document). The most robust approach is to use an array where the first element is longitude and the second is latitude:

location: [<longitude>, <latitude>]

Your Realm Object

The RChat app gives users the option to include their location in a chat message—this means that we need to include the location in the ChatMessage Object:

@objcMembers class ChatMessage: Object, ObjectKeyIdentifiable {
  …
    let location = List<Double>()
  …
    convenience init(author: String, text: String, image: Photo?, location: [Double] = []) {
        ...
    location.forEach { coord in
            self.location.append(coord)
      }
        ...
        }
    }
   ….
}

The location array that’s passed to that initializer is formed like this:

let location = LocationHelper.currentLocation
self.location = [location.longitude, location.latitude]

Location Data in Your Backend MongoDB Realm App

The easiest way to create your backend MongoDB Realm schema is to enable Development Mode—that way, the schema is automatically generated from your Swift Realm Objects.

This is the generated schema for our “ChatMessage” collection:

{
    "bsonType": "object",
    "properties": {
      "_id": {
        "bsonType": "string"
      },
      ...
      "location": {
        "bsonType": "array",
        "items": {
          "bsonType": "double"
        }
      }
    },
    "required": [
      "_id",
      ...
    ],
    "title": "ChatMessage"
}

This is a document that’s been created from a synced Realm ChatMessage object:

Screen capture of an Atlas document, which includes an array named location

Adding a Geospatial Index in Atlas

Now that you have location data stored in Atlas, it would be nice to be able to work with it—e.g., running geospatial queries. To enable this, you need to add a geospatial index to the location field.

From the Atlas UI, select the “Indexes” tab for your collection and click “CREATE INDEX”:

Atlas screen capture of creating a new index

You should then configure a 2dsphere index:

Atlas screen capture of creating a new 2dsphere index

Most chat messages won’t include the user’s location and so I set the sparse option for efficiency.

Note that you’ll get an error message if your ChatMessage collection contains any documents where the value in the location attribute isn’t in a valid geospatial format.

Atlas will then build the index. This will be very quick, unless you already have a huge number of documents containing the location field. Once complete, you can move onto the next section.

Plotting Your Location Data in MongoDB Charts

MongoDB Charts is a simple way to visualize MongoDB data. You can access it through the same UI as Realm and Atlas. Just click on the “Charts” button:

Atlas screen capture of MongoDB Charts button

The first step is to click the “Add Data Source” button:

Charts screen capture of adding a new data source

Select your Atlas cluster:

Charts screen capture of adding Atlas cluster as a data source

Select the RChat.ChatMessage collection:

Charts screen capture of selecting the ChatMessage collection in the RChat database

Click “Finish.” You’ll be taken to the default Dashboards view, which is empty for now. Click “Add Dashboard”:

Charts screen capture of adding a new dashboard

In your new dashboard, click “ADD CHART”:

Charts screen capture of adding a new chart

Configure your chart as shown here by:
– Setting the chart type to “Geospatial” and the sub-type to “Scatter.”
– Dragging the “location” attribute to the coordinates box.
– Dragging the “author” field to the “Color” box.

Charts screen capture of configuring a new chart

Once you’ve created your chart, you can embed it in web apps, etc. That’s beyond the scope of this article, but check out the MongoDB Charts docs if you’re interested.

Conclusion

SwiftUI makes it easy to embed Apple Maps in your SwiftUI apps. As with most Apple frameworks, there are extra maps features available if you break out from SwiftUI, but I’d suggest that the simplicity of working with SwiftUI is enough incentive for you to avoid that unless you have a compelling reason.

Accessing location information from within SwiftUI still feels a bit of a hack, but in reality, you cut and paste the helper code once, and then you’re good to go.

By storing the location as a [longitude, latitude] array (List) in your Realm database, it’s simple to sync it with MongoDB Atlas. Once in Atlas, you have the full power of MongoDB’s geospatial functionality to work your location data.

If you have questions, please head to our developer community website where the MongoDB engineers and the MongoDB community will help you build your next big idea with MongoDB.





New article on migrating Apple’s Scrumdinger tutorial app to Realm

Apple published a great tutorial to teach developers how to create iOS apps using Swift and SwiftUI. I particularly like it because it doesn’t make any assumptions about existing UIKit experience, making it ideal for developers new to iOS. That tutorial is built around an app named “Scrumdinger,” which is designed to facilitate daily scrum meetings. Apple’s Scrumdinger implementation saves the app data to a local file whenever the user minimizes the app, and loads it again when they open the app. It seemed an interesting exercise to modify Scrumdinger to use Realm rather than a flat file to persist the data. So. I wrote “Adapting Apple’s Scrumdinger SwiftUI Tutorial App to Use Realm” to step through what changes were required to rebase Scrumdinger onto Realm. An immediate benefit of the move is that changes are now persisted immediately, so nothing is lost if the device or app crashes. It’s beyond the scope of this article, but now that the app data is stored in Realm, it would be straightforward to add enhancements such as:
  • Search meeting minutes for a string.- Filter minutes by date or attendees.
  • Sync data so that the same user can see all of their data on multiple iOS (and optionally, Android) devices.
  • Use Realm Sync Partitions to share scrum data between team members.
  • Sync the data to MongoDB Atlas so that it can be accessed by web apps or through a GraphQL API




Building a Mobile Chat App Using Realm – Data Architecture

I’ve just built an iOS chat app using SwiftUI, Realm, and Realm Sync. I decided on a chat app as it makes an interesting case study for designing a data model and controlling who can access what data:

  • A chat message needs to be viewable by all members of a chat room and no one else.
  • New messages must be pushed to the chat room for all online members in real-time.
  • The app should notify a user that there are new messages even when they don’t have that chat room open.
  • Users should be able to observe the “presence” of other users (e.g., whether they’re currently logged into the app).
  • There’s no limit on how many messages users send in a chat room, and so the data structures must allow them to grow indefinitely.

Because this app’s data model (and the decisions taken when designing) serve as a great starting point for many different types of apps, I wrote it up in this HowTo article.

You can download all of the code from the GitHub repo.

Checkout Building a Mobile Chat App Using Realm – Data Architecture for all of the details.





Stitch & Mobile Webinar Questions & Replay

How do you test MongoDB Stitch functions, how do you store Stitch triggers, and what services can you integrate Stitch with? These were some of the great questions that were asked and answered in my recent webinar. You can watch the replay of “MongoDB Mobile and MongoDB Stitch – Introduction and Latest Developments” here, or read on as I share the answers to those questions here.

For those new to MongoDB Stitch, it’s the serverless platform from MongoDB that isolates complexity and ‘plumbing’ so you can build applications faster. Stitch went GA in June 2018, and we recently added a set of new capabilities, including global Stitch apps, more AWS services, a React Native SDK, and the beta for Stitch Mobile Sync. MongoDB Mobile is an embedded version of the MongoDB database that you can embed in your mobile or IoT apps.

Building mobile apps with MongoDB Mobile, MongoDB Stitch, and MongoDB Atlas

These are some of the questions I thought might be of interest:

How do you test Stitch functions?

The Stitch UI includes a console which can be used to test your Stitch functions; you can include console.log statements to add extra debug output to the console (they also get added to the log files for every function invoked from an incoming webhook, trigger, or the Stitch SDK).

You can also invoke your Stitch functions through the mongo shell. To do that, you’ll need to enable the MongoDB wire protocol so that the shell can talk to your Stitch app, then use the Stitch connection string provided. Once connected, you can call Stitch functions explicitly like this:

mongo> db.runCommand({callFunction: "morning", arguments: ["Billy"]})

{"ok" : 1,
 "response" : {"message" : "Good Morning Billy from andrew.morgan@mongodb.com"}
}

You can read more about this in this post which takes you through the process.

How do you store stitch triggers in your Git repo?

You can export your Stitch application from the Stitch UI or the Stitch CLI; the exported app is represented by a directory structure containing JSON and JavaScript files:

yourStitchApp/
├── stitch.json
├── secrets.json
├── variables.json
├── auth_providers/
│   └── <provider name>.json
├── functions/
│   └── <function name>/
│       ├── config.json
│       └── source.js
├── services/
│   └── <service name>/
│       ├── config.json
│       ├── incoming_webhooks/
│       │   ├── config.json
│       │   └── source.js
│       └── rules/
│           └── <rule name>.json
├── triggers/
│   └── <trigger name>/
│       ├── config.json
│       └── source.js
└── values/
    └── <value name>.json

You can then work on the trigger configuration and the associated function locally, source control the app using Git, or import it into a new App.

We saw a few integration provider logos in the presentation. Is there a page on the MongoDB site with the comprehensive list?

You can find the list of Stitch’s built-in service integrations in the Stitch documentation.

Note that if we don’t have a built-in integration for a particular service, then you can easily integrate it yourself, using the Stitch HTTP service and incoming webhooks. You can even export your new service integration to share with others.

Is MongoDB Mobile + Stitch Mobile Sync the same as a cache in a progressive app?

It certainly removes the need to have a separate cache, but it does much more. With MongoDB Mobile, the data is persistently stored on your device. You also have the full power of the MongoDB Query Language to perform sophisticated queries and aggregations on that local data. Changes made to the local database are pushed back to MongoDB Atlas, and from there to any other mobile devices configured to sync the same documents (e.g., for the same user running the app on another device).

How do I download & embed MongoDB Mobile?

You simply need to add 1 line to your Android or Xcode project to have access to the entire Stitch SDK, including the Stitch Local Database service (i.e.,the MongoDB Mobile database). The Stitch SDK includes the entire mobile and makes it very easy to use and consume, even if you’re just using the local MongoDB Mobile database and not Stitch.


Creating your first Stitch app? Start with one of the Stitch tutorials.

Want to learn more about MongoDB Stitch? Read the white paper.