Dispatch SDK is an expo based SDK that is written in Typescript and some Modules in Java/Kotlin. This sdk can be used to render execution task screens on ui, maintain their states, manage mts.
Currently supports SDK>=21 (Android)
-
Sync Manager
: Provides Sync Manager for syncing events & Docs in background with retry functionality. -
Execution Tasks
: Provides a list of Execution Tasks inbuilt UIs which are as follows.Deliver
Capture Input
Deliver Cash
Complete-Success
Complete-Failure
Pickup
Doodle
Form
Display
Verify Location
Verify Input
Scan
Image Capture
Init Payment
Process Payment
Complete Payment
Validate OTP
-
Execution Engine
: Provides methods for Executing a given workflow/Job in a dispatch` -
Firebase Cloud Messaging
:Provides method for receiving Firebase Cloud Messaging -
Async storage
:Provides Async storage for storing key value pairs -
Async Events
: Support for async events triggered on start and end of every execution task except end-state tasks, as for them a summary is generated at the end of completion of workflow.-
Format of events:
const eventName = `onTaskStart:{ET_Name}` | `onTaskEnd:{ET_name}` | `onTaskBack:{ET_name}` | `onScan:{ET_name}` | `onScanRemove:{ET_name}`;
-
Usage:
import { eventListener } from '@os1-platform/dispatch-mobile'; eventListener.on('{eventName}', (eventData) => { console.log(eventData); }); eventListener.remove('{eventName}');
-
Event data Payload:
onTaskStart event
:`onTaskStart:{ETCustomName}` = { "timestamp":"number", "taskId":"string", "etData":{ "jobId1":{ ...ET inputs }, "jobId2":{ ...ET inputs } } }
onTaskEnd event
|onTaskBack event
:`onTaskEnd:{ETCustomName}` = { "timestamp": "string", "taskId": "string", "etData":{ "jobId1":{ "success":"boolean", "eventCode":"string", "reasonCode":"string", ...ET output }, "jobId2":{ "success":"boolean", "eventCode":"string", "reasonCode":"string", ...ET output } }, "meta"?:{}, }
-
npx install-expo-modules
#using npm
npm install @os1-platform/dispatch-mobile
{
"dependencies": {
"@apollo/client": "^3.5.6",
"@expo-google-fonts/ibm-plex-sans": "*",
"@foxtrotplatform/platform-coreos-mts-sdk": "^0.5.10",
"@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.7",
"@react-native-firebase/analytics": "^14.2.2",
"@react-native-firebase/app": "^14.2.2",
"@react-native-firebase/crashlytics": "^14.2.2",
"@react-native-firebase/messaging": "^14.2.2",
"@react-native-firebase/remote-config": "^14.2.2",
"@react-navigation/native": "^6.0.6",
"@react-navigation/native-stack": "^6.2.5",
"@sentry/react-native": "^3.2.13",
"axios": "^0.24.0",
"expo": "~47.0.13",
"expo-barcode-scanner": "~11.4.0",
"expo-blur": "~11.0.0",
"expo-camera": "~13.1.0",
"expo-file-system": "~15.1.1",
"expo-font": "~11.0.1",
"expo-image-manipulator": "~11.0.0",
"expo-image-picker": "~14.1.0",
"expo-location": "~15.0.1",
"expo-sqlite": "~11.0.0",
"graphql": "^16.2.0",
"react": "^18.0.0",
"react-native": "^0.69.5",
"react-native-dropdown-picker": "^5.4.0",
"react-native-get-random-values": "^1.8.0",
"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"
}
}
Add this line in gradle.properties file for increasing storage (AsyncStorage_db_size_in_MB=10)
android:allowBackup="false"
tools:replace="android:allowBackup"
<activity
android:name=".MainActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:label="@string/app_name"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize"
android:exported="true">
Add jcenter() if not added already added in the Project-level build.gradle (/build.gradle): (Android only)
buildscript {
repositories {
...
jcenter()
}
}
allprojects {
repositories {
...
jcenter()
...
}
}
const { getDefaultConfig } = require('metro-config');
const { resolver: defaultResolver } = getDefaultConfig.getDefaultValues();
exports.resolver = {
...defaultResolver,
sourceExts: [...defaultResolver.sourceExts, 'cjs'],
};
interface EventMapping {
eventCode: string;
successEvent: boolean;
description: string;
}
interface DispatchSDKConfig {
...previous configs
debugLevelLog?: Boolean;
callbacks?: {
fetchPhoneNumbersCallback?: (sdsIds: string[]) => Promise<{ key: string; value: string[]}>;
};
}
const fetchPhoneNumbersCallback = (ids: string[]) => {
console.log('sds ids: ', ids);
return {
key: 'phoneNumber', //This key is same, what was being sent in meta as key
value: ['8888888888'],
};
};
import { DispatchSDKManager } from '@os1-platform/dispatch-mobile';
await DispatchSDKManager.getInstance().initDispatchSDK({
userName: 'testuser',
userID: 'testID',
tenantID: tenantId,
tenantBaseURL: 'baseURL',
accessToken: 'accessToken',
headers: {
'x-user-id': 'testID',
'x-coreos-tid': tenantId,
'x-coreos-access': accessToken,
'Content-Type': 'application/json',
'x-coreos-request-id': new Date().getTime(),
'x-coreos-userinfo': JSON.stringify({ id: 'testID' }),
},
// Used for Scan ET dropdown list => eventMapping: EventMapping[]
eventMapping: [
{
eventCode: 'E-010',
successEvent: true,
description: 'Success',
},
{
eventCode: 'E-011',
successEvent: false,
description: 'Failure',
},
],
callbacks: { fetchPhoneNumbersCallback }, // Callback to fetch mobile number based on the sds-ids passed
});
interface Job {
id: string;
jobRef: string;
jobWorkflowId: string;
status: string;
subStatus?: string | null;
containers: Array<Container>;
customData: any;
displayInfo: any;
start: string;
objectives: Array<Objective>;
}
interface Workflow {
name: string;
id: string;
tag: IWfTag[];
description: string;
flows: Array<ExecutionTask>;
expire?: boolean;
meta?: string;
}
interface Workflows {
[workflowId:string]: Workflow;
}
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: string,
dispatchJobs: Job[],
dispatchWorkflows: Workflows,
maxTaskReattempt?: number
);
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,
initRoute: 'TaskDetail',
mergedWI: {
mwId: route.params.moId,
customEventsData: {},
statusFilter: ExecutionStatus[]
},
successRoute: 'SuccessScreen',
failureRoute: 'FailureScreen',
meta: META,
});
export interface ObjectiveSummary {
entityCode: string;
eventCode: string;
reasonCode: string;
reasonCodeDesc?: string;
}
// When navigating to success or failure route, following are the route params:
{
summary: {
success?: ObjectiveSummary[];
failure?: ObjectiveSummary[];
},
objSuccess: boolean,
}
// getObjectiveTaskList
public async getMergedObjectiveList getObjectiveTaskList(
status: ExecutionStatus[]
): Promise<IObjectiveTask[]> {
return [];
}
// getObjectiveTaskDetails
public async getMergedObjectiveDetails getObjectiveTaskDetails(
mwId: string,
status: ExecutionStatus[]
): Promise< (IObjectiveTask & { workflowData: StoredWorkflowInstance[] | [] }) | {} >
{
return [];
}
// Inventory Data
public async getRiderInventory(): Promise<IDispatch.ItemsInRiderCustody> {
return {}
}
export interface IObjectiveTask {
id: string;
status: ExecutionStatus;
tags: IWfTag[];
inputs: any;
instanceCount: number;
location: Location;
contact: string[];
}
export interface StoredWorkflowInstance {
id: string;
mergedObjectiveId: string;
jobId: string;
scannableId: string;
initialMergedObjectiveId: string;
status: ExecutionStatus;
jobWorkflowId: string;
workflowId: string;
displayInfo: any;
cashAmount: number;
}
enum ExecutionStatus {
READY = 'READY',
IN_PROGRESS = 'IN_PROGRESS',
NOT_READY = 'NOT_READY',
COMPLETED_SUCCESS = 'COMPLETED-SUCCESS',
COMPLETED_FAILURE = 'COMPLETED-FAILURE',
}
interface IWfTag {
name: string;
value: string;
}
export interface Location {
locationId: string;
address?: any;
geolocation?: any;
}
export interface ItemsInRiderCustody {
shipments: {
totalCount: number;
deliveredCount: number;
pickedCount: number;
};
cash: {
expectedAmount: number;
collectedAmount: number;
};
}
- Create a firebase project (Check Firebase instructions for creating 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.android.tools.build:gradle:7.1.1")
classpath("com.google.gms:google-services:4.3.10") // Google Services plugin
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
// ...
repositories {
// Check that you have the following line (if not, add it):
google() // Google's Maven repository
// ...
}
}
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();
Note : Call this function in the root component i.e in index.js file of the app
import { registerBackgroundHandler } from '@os1-platform/dispatch-mobile';
registerBackgroundHandler((message: object) => {
// Handle FCM Message here
});
import { useFCMMessage } from '@os1-platform/dispatch-mobile';
const fcmMessage = useFCMMessage();
if (fcmMessage != null) {
// update UI here to show the message
}
OR
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 {}
}
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
}
let granted = await MtsLib.requestPermissionsForMTS();
// if granted = true : all permissions granted
// granted = false : one or more permissions denied
import type { MTSInitRequest } from '@foxtrotplatform/platform-coreos-mts-sdk';
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://delhivery.aws.preprod.fxtrt.io/app/dispatch/v1/api/mts/',
accessToken: 'token',
tenantID: tenantId,
};
await MtsLib.initMTS(mtsInitReq);
//See Error Codes for Possible error types
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);
await MtsLib.publishEvent('TESTEVENT', { battery: 56, network: 100 });
// To stop mts
await MtsLib.stopMTS();
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';
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');
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();
import { SdkUtils } from '@os1-platform/dispatch-mobile';
await SdkUtils.getRemoteConfig(3000);
// 3000 is the number of seconds to cache the config
/**
* 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)
);
/**
* Opens & Install an APK file
* @param uri - source of apk file
*/
await SdkUtils.openAPKFile(result.uri);
/**
*
* @param eventName-> string
* @param tag -> string
* @param message -> string
*/
await Logger.getInstance().sendToFirebaseAnalytics('ev_name', 'tag', 'message');
/**
*
* @param TAG
* @param message
* @param logType
*/
Logger.getInstance().logEvent('tag', 'message', LOG_TYPE.SDK_ERROR);
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',
}