Repository: kiwicom/mobile
Pull request #1024 (2018-08-24 15:42:22)

Relay: don't call network when offline


Patch:
diff --git a/packages/relay/index.js b/packages/relay/index.js
index ec5a0cd6..c8055d32 100644
--- a/packages/relay/index.js
+++ b/packages/relay/index.js
@@ -2,9 +2,11 @@
 
 import * as React from 'react';
 import Relay from 'react-relay'; // eslint-disable-line no-restricted-imports
+import { Alert } from '@kiwicom/mobile-localization';
 
 import PublicEnvironment from './src/PublicEnvironment';
 import PrivateEnvironment from './src/PrivateEnvironment';
+import ConnectionManager from './src/ConnectionManager';
 
 export {
   graphql,
@@ -27,6 +29,11 @@ export const commitMutation = (
   config: CommitMutationConfig,
   token?: string,
 ) => {
+  if (ConnectionManager.isConnected() === false) {
+    Alert.translatedAlert(null, { id: 'relay.query_renderer.no_connection' });
+    config.onCompleted(null, 'No connection');
+    return;
+  }
   if (token) {
     return Relay.commitMutation(
       PrivateEnvironment.getEnvironment(token),
diff --git a/packages/relay/src/ConnectionManager.js b/packages/relay/src/ConnectionManager.js
new file mode 100644
index 00000000..b9c36ae6
--- /dev/null
+++ b/packages/relay/src/ConnectionManager.js
@@ -0,0 +1,23 @@
+// @flow strict
+
+import { NetInfo } from 'react-native';
+
+type ConnectionType = 'none' | 'wifi' | 'cellular' | 'unknown';
+
+class ConnectionManager {
+  connectionType: ConnectionType;
+
+  constructor() {
+    NetInfo.addEventListener('connectionChange', this.handleConnectionChange);
+  }
+
+  handleConnectionChange = ({ type }: { type: ConnectionType }) => {
+    this.connectionType = type;
+  };
+
+  isConnected = () => {
+    return this.connectionType !== 'none' && this.connectionType !== 'unknown';
+  };
+}
+
+export default new ConnectionManager();
diff --git a/packages/relay/src/Environment.js b/packages/relay/src/Environment.js
index 0b87b5da..283c437c 100644
--- a/packages/relay/src/Environment.js
+++ b/packages/relay/src/Environment.js
@@ -11,6 +11,8 @@ import {
 } from 'relay-runtime';
 import { AsyncStorage } from 'react-native';
 
+import ConnectionManager from './ConnectionManager';
+
 type FetcherResponse = {|
   +data: Object,
   +errors?: $ReadOnlyArray<Object>,
@@ -66,7 +68,7 @@ export default function createEnvironment(accessToken: string = '') {
     cacheConfig,
   ): Promise<FetcherResponse> => {
     return Observable.create(observer => {
-      if (cacheConfig.force === true) {
+      if (cacheConfig.force === true && ConnectionManager.isConnected()) {
         return fetchFromTheNetwork(
           networkHeaders,
           operation,
@@ -89,6 +91,9 @@ export default function createEnvironment(accessToken: string = '') {
               // load it from the memory first and call the API after that
               // this will make the UI feel really fast
               observer.next(store.lookup(operationSelector.root));
+              if (!ConnectionManager.isConnected()) {
+                observer.complete();
+              }
             }
           }
         })
@@ -97,9 +102,10 @@ export default function createEnvironment(accessToken: string = '') {
           console.warn(error);
         })
         .finally(() => {
-          // we are always trying to fetch from the network in order to update
-          // the cache - this could be done only when online in the future
-          fetchFromTheNetwork(networkHeaders, operation, variables, observer);
+          // Only fetch from network if we are connected
+          if (ConnectionManager.isConnected()) {
+            fetchFromTheNetwork(networkHeaders, operation, variables, observer);
+          }
         });
     });
   };


Test patch:
diff --git a/packages/relay/__tests__/index.test.js b/packages/relay/__tests__/index.test.js
new file mode 100644
index 00000000..b6937837
--- /dev/null
+++ b/packages/relay/__tests__/index.test.js
@@ -0,0 +1,66 @@
+// @flow strict
+
+import Relay from 'react-relay'; // eslint-disable-line no-restricted-imports
+
+import { commitMutation } from '../';
+import ConnectionManager from '../src/ConnectionManager';
+import PrivateEnvironment from '../src/PrivateEnvironment';
+import PublicEnvironment from '../src/PublicEnvironment';
+
+jest.mock('react-relay', () => ({
+  commitMutation: jest.fn(),
+}));
+
+jest.mock('@kiwicom/mobile-localization', () => ({
+  Alert: {
+    translatedAlert: jest.fn(),
+  },
+}));
+
+jest.mock('../src/ConnectionManager.js', () => {
+  let desiredReturn = false;
+  return {
+    isConnected: () => desiredReturn,
+    setDesiredReturn: (input: boolean) => {
+      desiredReturn = input;
+    },
+  };
+});
+
+jest.mock('../src/PublicEnvironment.js', () => ({
+  getEnvironment: jest.fn(),
+}));
+jest.mock('../src/PrivateEnvironment.js', () => ({
+  getEnvironment: jest.fn(),
+}));
+jest.mock('../src/Environment.js');
+
+const config = {
+  mutation: 'test',
+  variables: {},
+  onCompleted: jest.fn(),
+};
+
+describe('commitMutation', () => {
+  it('does not call network if there is no connection', () => {
+    commitMutation(config);
+    expect(Relay.commitMutation).not.toHaveBeenCalled();
+    expect(config.onCompleted).toHaveBeenCalledWith(null, 'No connection');
+  });
+
+  it('does calls commit mutation and PrivateEnvironment if there is connection and a token', () => {
+    // $FlowExpectedError: method does not exist on class, but in mock
+    ConnectionManager.setDesiredReturn(true);
+    commitMutation(config, 'token');
+    expect(Relay.commitMutation).toHaveBeenCalled();
+    expect(PrivateEnvironment.getEnvironment).toHaveBeenCalledWith('token');
+  });
+
+  it('does calls commit mutation and PublicEnvironment if there is connection but not a token', () => {
+    // $FlowExpectedError: method does not exist on class, but in mock
+    ConnectionManager.setDesiredReturn(true);
+    commitMutation(config);
+    expect(Relay.commitMutation).toHaveBeenCalled();
+    expect(PublicEnvironment.getEnvironment).toHaveBeenCalledWith();
+  });
+});
diff --git a/packages/relay/src/__tests__/ConnectionManager.test.js b/packages/relay/src/__tests__/ConnectionManager.test.js
new file mode 100644
index 00000000..20247d50
--- /dev/null
+++ b/packages/relay/src/__tests__/ConnectionManager.test.js
@@ -0,0 +1,27 @@
+// @flow
+
+import ConnectionManager from '../ConnectionManager';
+
+describe('ConnectionManager', () => {
+  describe('isConnected', () => {
+    it('returns false if connectionType is none', () => {
+      ConnectionManager.handleConnectionChange({ type: 'none' });
+      expect(ConnectionManager.isConnected()).toBe(false);
+    });
+
+    it('returns false if connectionType is unknown', () => {
+      ConnectionManager.handleConnectionChange({ type: 'unknown' });
+      expect(ConnectionManager.isConnected()).toBe(false);
+    });
+
+    it('returns true if connectionType is wifi', () => {
+      ConnectionManager.handleConnectionChange({ type: 'wifi' });
+      expect(ConnectionManager.isConnected()).toBe(true);
+    });
+
+    it('returns true if connectionType is cellular', () => {
+      ConnectionManager.handleConnectionChange({ type: 'cellular' });
+      expect(ConnectionManager.isConnected()).toBe(true);
+    });
+  });
+});