Dispatch SDK

For every Execution Task, a corresponding pre-coded module is expected in the app as part of SDK. This module executes the Task on the device running the Delivery Agent app. Thus, the Dispatcher SDK allows you to build this app with the minimum amount of effort.

The Mobile App SDK will orchestrate the execution of Tasks. Objective status updates will flow back as objective events to dispatch service through API calls made by the SDK and they will be stored in the Objective Events Store. This SDK can be used to render execution task screens on UI, maintain their states, and manage MTS.

Dispatch SDK is an expo-based SDK that is written in Typescript and some Modules in Java/Kotlin. Currently supports SDK>=21 (Android).

Features

  • Sync Manager: Provides Sync Manager for syncing events & Docs in the background with retry functionality.
  • Execution Tasks: Provides a list of Execution Tasks which are as follows: Deliver, Capture Input, Deliver Cash, Complete-Success, Complete-Failure, Pickup, Doodle, Forms, Display, Verify Location, Verify OTP, Verify String, Scan QR/Barcode, Image Capture, Init Payment, Process Payment, and Complete Payment.
  • Execution Engine: Provides methods for Executing a given Workflow/Job in a dispatch.
  • Firebase Cloud Messaging: Provides a method for receiving Firebase Cloud Messaging.
  • Async Storage: Provides async storage for storing key-value pairs.
  • Higher-Order ETs: Provides a way to customize Execution Task UI.

Installation

Setup Expo (For non-expo projects only)

npx install-expo-modules

Install Dispatch SDK Package

npm install @os1-platform/[email protected]_version
yarn add @os1-platform/[email protected]_version

Install these dependencies

{
  "dependencies": {
    "@apollo/client": "^3.5.6",
    "graphql": "^16.2.0",
    "@expo-google-fonts/ibm-plex-sans": "*",
    "@os1-platform/mts-mobile": "^1.0.0",
    "@react-native-async-storage/async-storage": "^1.15.5",
    "@react-native-community/datetimepicker": "^3.5.2",
    "@react-native-community/netinfo": "^6.0.2",
    "@react-native-community/slider": "^4.1.8",
    "@react-native-firebase/analytics": "^14.1.0",
    "@react-native-firebase/app": "^14.1.0",
    "@react-native-firebase/messaging": "^14.1.0",
    "@react-native-firebase/remote-config": "^14.1.0",
    "@react-navigation/native": "^6.0.6",
    "@react-navigation/native-stack": "^6.2.5",
    "axios": "^0.24.0",
    "expo": "^43.0.4",
    "expo-barcode-scanner": "^11.2.1",
    "expo-file-system": "~13.0.3",
    "expo-font": "~10.0.3",
    "expo-image-manipulator": "^10.1.2",
    "expo-image-picker": "^11.0.3",
    "expo-location": "^13.0.4",
    "expo-sqlite": "^10.0.3",
    "react": "17.0.2",
    "react-native": "0.66.1",
    "react-native-paper": "^4.9.2",
    "react-native-safe-area-context": "^3.3.2",
    "react-native-screens": "^3.9.0",
    "react-native-vector-icons": "^9.0.0"
  }
}

Async Storage Size Increase

Add the following line in gradle.properties file for increasing storage (AsyncStorage_db_size_in_MB=10)

android:allowBackup="false"
tools:replace="android:allowBackup"

Add jcenter() if not added already added in the Project-level build.gradle (/build.gradle): (Android only)

buildscript {

    repositories {
        ...
        jcenter()
    }
}

allprojects {
    repositories {
        ...
        jcenter()
        ...
    }
}

Fix for Apollo GraphQL Client (metro.config.js)

Add the following changes in metro.config.js file. Issue Link

const { getDefaultConfig } = require('metro-config');
const { resolver: defaultResolver } = getDefaultConfig.getDefaultValues();
exports.resolver = {
  ...defaultResolver,
  sourceExts: [...defaultResolver.sourceExts, 'cjs'],
};

Usage

Init Dispatch SDK

import { DispatchSDKManager } from '@os1-platform/dispatch-mobile';

await DispatchSDKManager.getInstance().initDispatchSDK({
  userName: 'testuser',
  userID: 'testID',
  tenantID: 'TENANTID',
  tenantBaseURL: 'baseURL',
  accessToken: 'accessToken',
});

Init Execution Engine

import { DispatchStateContainer } from '@os1-platform/dispatch-mobile';

/**
 * Call this function when dispatch data is fetched successfully
 * @param dispatchID
 * @param jobs
 * @param logging
 * @param maxTaskReattempt
 */

await DispatchStateContainer.getInstance().initDispatchExecutor(
  dispatchID,
  dispatch.data.dispatch.jobs.data as Job[],
  false
);

Query Objective Status

import { DispatchStateContainer } from '@os1-platform/dispatch-mobile';

/**
 *
 * @param objRef
 * @param jobID
 */

DispatchStateContainer.getInstance().fetchObjectiveState(
  'objective_ref',
  'job_id'
);

Start Objective Execution

interface sdkError {
  code: string;
  message: string;
}
// Error Handling from the calling Screen
React.useEffect(() => {
  if (route.params?.sdkError) {
    console.log(JSON.stringify(route.params.sdkError));
    Alert.alert('Error', JSON.stringify(route.params.sdkError));
  }
}, [route.params?.sdkError]);

navigation.navigate('DispatchExec', {
  success: true,
  objectives: [], // Pass objective List
  successRoute: 'SuccessScreen',
  failureRoute: 'FailureScreen',
  initRoute: 'TaskDetail',
  // Calling Screen name (optional parameter). Used to throw errors back to calling screen
});

FCM

Setup (Android only)

Add the Firebase Android configuration file to your app:

  • Create a firebase project (Check Firebase instructions for creating an app).
  • Click Download google-services.json to obtain your Firebase Android config file (google-services.json).
  • Move your config file into the module (app-level) directory of your app.
  • To enable Firebase products in your app, add the google-services plugin to your Gradle files.

In your module (app-level) Gradle file (usually app/build.gradle), apply the Google Services Gradle plugin:

apply plugin: 'com.android.application'
// Add the following line:
apply plugin: 'com.google.gms.google-services'  // Google Services plugin

android {
  // ...
}

In your root-level (project-level) Gradle file (build.gradle), add rules to include the Google Services Gradle plugin. Check that you have Google's Maven repository, as well.

buildscript {

  repositories {
    // Check that you have the following line (if not, add it):
    google()  // Google's Maven repository
  }

  dependencies {
    // ...

    // Add the following line:
    classpath 'com.google.gms:google-services:4.3.10'  // Google Services plugin
  }
}

allprojects {
  // ...

  repositories {
    // Check that you have the following line (if not, add it):
    google()  // Google's Maven repository
    // ...
  }
}

Get FCM Token

import {
  getFCMToken,
  requestFirebasePermissions,
} from '@os1-platform/dispatch-mobile';
//Get FCM Token
let token = await getFCMToken();

//Request Notifications permissions (ios)
// It will ask for user permissions for showing alert/notifications
let enabled = await requestFirebasePermissions();

Background FCM Messages

Register callback for receiving FCM Messages in the background.

📘

NOTE

Call this function in the root component, i.e, in the index.js file of the app.

import { registerBackgroundHandler } from '@os1-platform/dispatch-mobile';

registerBackgroundHandler((message: object) => {
  // Handle FCM Message here
});

Foreground FCM Messages

useFcmMessage() custom hook in functional components to receive FCM Messages in Foregound.

import { useFCMMessage } from '@os1-platform/dispatch-mobile';

const fcmMessage = useFCMMessage();
if (fcmMessage != null) {
  // update UI here to show the message
}

OR

Extend FCM base class in case of class components

import { FCM } from '@os1-platform/dispatch-mobile';

class ClassComponent extends FCM {
  //implement these methods
  handleFcmMessage(remoteMessage: object): void {}

  //implement these methods
  handleNotification(remoteMessage: object): void {}
}

MTS

In the case of the dispatch mobile app, the Location Tracking module of the SDK provides a configurable way to capture and stream location data from the mobile device to the MTS service, without any user intervention. To learn more, see Dispatch Documentation.

MTS Default Config

export class MTSDefaults {
  locationFrequency: number = 10000; // in milli seconds (no. of seconds after which location updates will happen)
  distanceAccuracyLimit: number = 250; // in metres
  speedLimit: number = 28; // in m/s
  mode: MTSMode = MTSMode.HYBRID;
  environment: MTSEnv = MTSEnv.DEV;
  batchSize: number = 25;
  isMqttCleanSession: boolean = true;
  mqttKeepAliveInterval: number = 15 * 60; //in seconds
  maxLocationAge: number = 15000; // in milliseconds
  maxTraceSession: number = 24 * 3600 * 100; //in milliseconds
  isOdometerEnabled: boolean = true;
  retriesBeforeFallback: number = 1;
  httpFailureLimit: number = 5;
  dataSendDelay: number = 30000; // in milli seconds
  alarmTime: number = 60000; // in milli seconds
  missingSeqCheckDuration: number = 5 * 60 * 1000; // in milli seconds
  odometerPushFrequency: number = 5 * 60 * 1000; // in milli seconds
  qosLevel: number = 1; // values can be 0 ,1
}

Check For Mandatory MTS Permissions

let granted = await MtsLib.requestPermissionsForMTS();
// if granted = true : all permissions granted
// granted = false : one or more permissions denied

Init MTS

import type { MTSInitRequest } from '@os1-platform/mts-mobile';

let mtsDefaults = new MtsLib.MTSDefaults();
mtsDefaults.speedLimit = 5000;
mtsDefaults.locationFrequency = 10000;
mtsDefaults.environment = MtsLib.MTSEnv.PRE_PROD;
mtsDefaults.isOdometerEnabled = false;

//Change MTS default values as per your use case

// ...Use this for Initiating MTS
let mtsInitReq: MTSInitRequest = {
  appName: 'app_name',
  appVersion: '1',
  mtsDeviceID: 'deviceId', // use FCM ID here
  configData: mtsDefaults,
  baseURL: 'https://{tenantId}.example.io/{mtsEndpoint}',
  accessToken: 'token',
  tenantID: 'TENANTID',
};
await MtsLib.initMTS(mtsInitReq);
//See Error Codes for Possible error types

Start MTS

let startReq: MTSStartRequest = {
  accessToken: 'token', // update access token
  resetSequence: false,
  dispatchID: '12345', // pass dispatch ID here
  expiryTime: Date.now() + 24 * 2600 * 1000, // expiry time after which MTS will stop automatically
};
await MtsLib.startMTS(startReq);

Publish Event

await MtsLib.publishEvent('TESTEVENT', { battery: 56, network: 100 });

Stop MTS

await MtsLib.stopMTS();

Error Codes

PERMISSIONS_ERROR[2500] = 'Mandatory Android Permissions not provided';
MTS_INIT_ERROR[2501] = 'MTS INIT Not called! MTS Device ID is Empty';
PARAM_MISSING[2502] = 'Mandatory Paramater is missing in request';

Sync Manager

Import Sync Manager

import { AppSyncManager, SdkSyncType } from '@os1-platform/dispatch-mobile';

// Start Events sync
await AppSyncManager.getInstance().startSyncing(
  false, // pass true for force sync
  SdkSyncType.EVENTS_SYNC
);

//Start Documents sync
await AppSyncManager.getInstance().startSyncing(
  false, // pass true for force sync
  SdkSyncType.DOCUMENT_SYNC
);

//Get All Events By Dispatch ID
await AppSyncManager.getInstance().getAllEvents('dsp_id');

//Get all documents By Dispatch ID
await AppSyncManager.getInstance().getAllDocuments('dsp_id');

Start Sync Manager as a Foreground Service in android

import { NativeSyncManager } from '@os1-platform/dispatch-mobile';

NativeSyncManager.startSyncManager(
  interval,
  notificationTitle,
  notificationText
);
// interval will be in seconds
NativeSyncManager.startSyncManager(
  2000,
  'Dispatch Service',
  'Syncing events...'
);

//To stop the foreground android service
NativeSyncManager.stopSyncManager();

SDK Utility Methods

import { SdkUtils } from '@os1-platform/dispatch-mobile';

await SdkUtils.getRemoteConfig(3000);
// 3000 is the number of seconds to cache the config

Download API from Public URL

/**
 * Function to download apk file from a public URL
 * @param apkURL - URL where apk is hosted
 * @param version - expected version of apk (used for naming the file)
 * @param callback - callback for getting progress of download
 */
await SdkUtils.downloadAPK('https://apk_url.com', '1', (progress) =>
  console.log(progress.totalBytesWritten)
);

Open and Install an APK file

/**
 * Opens & Install an APK file
 * @param uri - source of apk file
 */

await SdkUtils.openAPKFile(result.uri);

Send events to Firebase analytics

/**
 *
 * @param eventName-> string
 * @param tag -> string
 * @param message -> string
 */
await Logger.getInstance().sendToFirebaseAnalytics('ev_name', 'tag', 'message');

Log events to console

/**
 *
 * @param TAG
 * @param message
 * @param logType
 */
Logger.getInstance().logEvent('tag', 'message', LOG_TYPE.SDK_ERROR);

Error Codes

const enum BaseErrorCodes {
  InvalidArgumentError = '100100',
  InvalidBaseURL = '100101',
  SyncManagerNotInitialized = '100102',
  MissingOrInvalidProps = '100103',
  SQLiteDBIssue = '100104',
  AppSyncNotInitialized = '100105',
  FMS_FOLDER_CREATION_ERROR = '100106',
  REASON_CODE_API_ERROR = '100107',
  MERGING_ERROR = '100108',
  LOCATION_PERMISSION_DENIED = '100109',
  CAMERA_PERMISSION_DENIED = '100110',
  STORAGE_PERMISSION_DENIED = '100111',
  GRAPHQL_CLIENT_NOT_INITIALIZED = '100112',
  FMS_GRAPHQL_API_ERROR = '100113',
  INTERNET_NOT_ENABLED = '100114',
  LOCATION_OR_GPS_NOT_ENABLED = '100115',
  EXECUTION_ENGINE_ERROR = '100116',
  UNEXPECTED_ERROR = '100117',
}