@os1-platform/dispatch-mobile
v2.1.26
Published
Dispatch SDK React Native Package
Downloads
251
Readme
React Native Dispatch SDK
Introduction
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)
Dispatch SDK provides the following features:
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
Collect Payment
Execution Engine
: Provides methods for Executing a given workflow/Job in a dispatch`Firebase Cloud Messaging
:Provides method for receiving Firebase Cloud MessagingAsync storage
:Provides Async storage for storing key value pairsAsync 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}`;
Format of click events for collect payment ET:
const eventName = `onClick:{clickEventName}:{etName}` enum clickEventName { QR_CODE, SEND_LINK, RESEND_LINK, CHECK_STATUS }
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"?:{}, }
onClick event for QR_CODE and CHECK_STATUS
{ timestamp: number; taskId: string; customData?: any; etData: { ...etInput } additionalData: { //This is the same refId passed by the app at the start of an objective "refId":"string", //This amount will be sum of amount for all the merged objective "money":{ "amount":"number", // sum of all the merged objectives "currency":"string" } } }
onClick event for SEND_LINK and RESEND_LINK
{ timestamp: number; taskId: string; customData?: any; etData: { ...etInput } additionalData: { //This is the same refId passed by the app at the start of an objective "refId": string, //This amount will be sum of amount for all the merged objective "money":{ "amount": number, // sum of all the merged objectives "currency": string } //This will contain either phoneNumber or email as per user selection "contactDetails": string, //Static ENV based payment landing page link "paymentLink": string } }
Dispatch SDK Installation
Setup Expo (For non-expo projects only)
npx install-expo-modules
Install Dispatch SDK Package
#using npm
npm install @os1-platform/dispatch-mobile
Install these dependencies
{
"dependencies": {
"@apollo/client": "^3.5.6",
"@expo-google-fonts/ibm-plex-sans": "*",
"@delhivery/platform-coreos-mts-sdk": "^2.0.3",
"@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"
}
}
Async Storage Size Increase
Add this line in gradle.properties file for increasing storage (AsyncStorage_db_size_in_MB=10)
Changes in Android Manifest File (Android only)
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()
...
}
}
Fix for Apollo GraphQL Client (metro.config.js)
Add the following changes in metro.config.js file
Issue Link https://github.com/apollographql/apollo-client/releases/tag/v3.5.4
const { getDefaultConfig } = require('metro-config');
const { resolver: defaultResolver } = getDefaultConfig.getDefaultValues();
exports.resolver = {
...defaultResolver,
sourceExts: [...defaultResolver.sourceExts, 'cjs'],
};
Dispatch SDK Usage
Init Dispatch SDK
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',
},
],
/*
* Callback to fetch mobile number based on the sds-ids passed
* This same callback will be used to fetch contacts for collect payment ET.
*/
callbacks: { fetchPhoneNumbersCallback },
});
Init Execution engine
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
);
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,
initRoute: 'TaskDetail',
mergedWI: {
mwId: route.params.moId,
customEventsData: {},
statusFilter: ExecutionStatus[]
},
successRoute: 'SuccessScreen',
failureRoute: 'FailureScreen',
meta: META,
paymentConfig: {
paymentRefId: string,
pgConfigId?: string,
userInfo?:{
phone?:{
countryCode:string
mobileNumber:string
}
}
}
});
Payment Handling
FCM Notification To handle the payment status notification using FCM, following data should be provided while sending the notification to app:
interface notificationPaymentStatus { refId: string; status: STATUS; }
Example: In notification payload data should have:
type: 'PAYMENT_COMPLETED'
and data.data should be json stringified.var payload = { "notification":{ "title":"Payment Completion", "body":"payment complete notify" }, "data": { "type": "PAYMENT_COMPLETED", "data": JSON.stringify({ "status":INITIATED | FAILED | SUCCESS, "refId":"ref:12345", }) } };
Notification Handling
- In case of status: success, the user will be redirected to the complete payment screen, from init/ contact details/process payment screens.
- In case of status:failure, 2.1. If the user is on the Init Payment screen, then it will stay on the same screen. 2.2. If the user is on the ContactDetails screen, then it will stay on the same screen. 2.3. If the user is on the Process Payment screen, then it will get redirected to the complete payment screen, and the complete payment screen will have the option for retry or cancel.
GO Back Feature
- If the user is on Init Payment screen and a payment is already initiated for that order(objective), and refId id is in-progress, then going back to previous ET should not be allowed and user to get redirected to home screen instead.
- If the user is on Init Payment screen and the payment is not yet initiated, then going back to previous ET as per the workflow should be allowed.
Objective Summary Route Params
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,
}
New functions
// 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 {}
}
Interfaces
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;
};
}
FCM
Setup (android only)
Add the Firebase Android configuration file to your app:
- 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
// ...
}
}
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 background
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
});
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
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 '@delhivery/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
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
// To 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 { 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
Download Firebase Config
import { SdkUtils } from '@os1-platform/dispatch-mobile';
await SdkUtils.getRemoteConfig(3000);
// 3000 is the number of seconds to cache the config
Download APK 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 & 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',
}