Supporting real-time updates in your mobile app

Configuring your server for real-time updates

A core concept of the GraphQL specification is an operation type called Subscription, they provide a mechanism for real time updates. To enable Subscriptions on the server, create an appropriate schema similar to the following example.

Procedure
  1. Create a schema to support subscriptions.

    For example, in the following schema a Task type is defined, followed by required Mutations and Subscriptions. When a new task is created, the resolver for that task publishes the result of this mutation to the TaskCreated channel.

    const { gql } = require('@aerogear/voyager-server')
    
    const typeDefs = gql `
    type Task {
      id: ID!
      title: String!
      description: String!
    }
    
    type Mutation {
      createTask(title: String!, description: String!): Task
    }
    
    type Subscription {
      taskCreated: Task
    }
    `
    
    const resolvers = {
        Mutation: {
            createTask: async (obj, args, context, info) => {
                const result = tasks.create(args)
                pubSub.publish('TaskCreated', {
                    taskCreated: result
                  });
                return result
            }
        },
        Subscription: {
            taskCreated: {
                subscribe: () => pubSub.asyncIterator('TaskCreated')
            }
      },
    }
    The pubSub mechanism used in the above example is deliberately ommited to focus on the GraphQL aspects of Subscriptions. For more information on the available PubSub implementations see: PubSub Implementations.
  2. Use the subscriptions-transport-ws package to set up the server.

    For more information on how to use and configure SubscriptionServer see the Apollo Subscription Server documentation.

    Using the example server described in [sync-server-getting-started] you can add subscriptions as follows by introducing http and wraping our express app:

    const http = require('http')
    const { SubscriptionServer } = require('subscriptions-transport-ws')
    const { execute, subscribe } = require('graphql')
    
    const httpServer = http.createServer(app)
    
    server.applyMiddleware({ app })
    
    httpServer.listen(4000, () => {
      new SubscriptionServer ({
        execute,
        subscribe,
        schema: server.schema
      }, {
        server: httpServer,
        path: '/graphql'
      })
    })
For information on how to protect this subscription server with Identity Management see [sync-server-auth].
Additional Information

Implementing real-time updates on on the client

A core concept of the GraphQL specification is an operation type called Subscription, they provide a mechanism for real time updates. For more information on GraphQL subscriptions see the Subscriptions documentation.

To do this GraphQL Subscriptions utilise websockets to enable clients to subscribe to published changes.

The architecture of websockets is as follows:

  • Client connects to websocket server.

  • Upon certain events, the server can publish the results of these events to the websocket.

  • Any currently connected client to that websocket receives these results.

  • The client can close the connection at any time and no longer receives updates.

Websockets are a perfect solution for delivering messages to currently active clients. To receive updates the client must be currently connected to the websocket server, updates made over this websocket while the client is offline are not consumed by the client. For this use case Push Notifications are recommended.

Voyager Client comes with subscription support out of the box including auto-reconnection upon device restart or network reconnect. To enable subscriptions on your client set the following paramater in the Voyager Client config object. A DataSyncConfig interface is also available from Voyager Client if you wish to use it.

Setting up a client to use subscriptions

To set up a client to use subscriptions:

  1. Provide a wsUrl string in the config object as follows:

    const config = {
        wsUrl: "ws://<your_websocket_url>"
    }

    where <your_websocket_url> is the full URL of the websocket endpoint of your GraphQL server.

  2. Use the object from step 1 to initialise Voyager Client:

    const { createClient } = require("@aerogear/voyager-client");
    
    const client = createClient(config)

Using Subscriptions

A standard flow to utilise subscriptions is as follows:

  1. Make a network query to get data from the server

  2. Watch the cache for changes to queries

  3. Subscribe to changes pushed from the server

  4. Unsubscibe when leaving the view where there is an active subscription

In the three examples below, subscribeToMore ensures that any further updates received from the server force the updateQuery function to be called with subscriptionData from the server.

Using subscribeToMore ensures the cache is easily updated as all GraphQL queries are automatically notified.

For more information, see the subscribeToMore documentation.

getTasks() {
  const tasks = client.watchQuery({
    query: GET_TASKS
  });

  tasks.subscribeToMore({
    document: TASK_ADDED_SUBSCRIPTION,
    updateQuery: (prev, { subscriptionData }) => {
    // Update logic here.
    }
  });
  return tasks;
}

To allow Voyager Client to automatically generate the updateQuery function for you, please see the Cache Update Helpers section.

You can then use this query in our application to subscribe to changes so that the front end is always updated when new data is returned from the server.

this.tasks = [];
this.getTasks().subscribe(result => {
  this.tasks = result.data && result.data.allTasks;
})

Note that it is also a good idea to unsubscribe from a query upon leaving a page. This prevents possible memory leaks. This can be done by calling unsubscribe() as shown in the following example. This code should be placed in the appropriate place.

this.getTasks().unsubscribe();

Handling network state changes

When using subscriptions to provide your client with realtime updates it is important to monitor network state because the client will be out of sync if the server if updated when the the client is offline.

To avoid this, Voyager Client provides a NetworkStatus interface which can be used along with the NetworkInfo interface to implement custom checks of network status.

For more information about how to import and configure a custom network status checker, see Advanced Topics.

Use the following example to re-run a query after a client returns to an online state:

const { CordovaNetworkStatus, NetworkInfo } = require("@aerogear/voyager-client");
const networkStatus = new CordovaNetworkStatus();

networkStatus.onStatusChangeListener({
  onStatusChange(networkInfo: NetworkInfo) {
    const online = networkInfo.online;
    if (online) {
      client.watchQuery({
        query: GET_TASKS
      });
    }
  }
});