Getting Started

Building a Basic "Proof of Delivery" Activity

This is an introductory tutorial that shows you how you can build barcode scanning solutions based on Flow. As an example solution, we will create a "Proof of Delivery" activity for employees of an imaginary package delivery service. Activities are workflows that can be launched by the user through the Flow app on the mobile phone or tablet. The activity that we will build during this tutorial will offer the following functionality:

  • When delivering a shipment (consisting of one or more packages), the user can scan the barcode of any package. Based on the information in the barcode, the system will then ask the user to scan all remaining packages (if any) to ensure the shipment is complete.
  • When all packages of the shipment have been scanned, the user can ask the recipient sign on the device's screen to confirm the delivery.
  • The collected information (barcodes, timestamps, signatures, etc.) is stored in the device's local database from where it will be automatically synchronized to the backend as soon as internet connectivity is available.
  • The collected information can be viewed or downloaded via the Flow web dashboard or accessed via the Flow REST API.

Thanks to Flow's smart synchronization™ feature, our activity will work without limitation even when the device is offline and has no internet access.

Step 1: Create new activity

As a first step, we will need to create a new activity. In Flow's web dashboard, click the "Activities" tab and select "Run your own code" in the "Add Activity" section. Give it any name you like, select all barcode configurations, choose JavaScript as the language and save your new activity.

You will now be taken to a web editor that shows these three files (among others) that have been generated automatically: src/scripts/app.js, src/views/view.html and src/styles/default.css.

src/scripts/app.js:

function App(){
    this.start = function(){
        Scandit.Ui.Views.load('src/views/view.html', {template_variable: 'Hello World'});
    };
}

var app = new App();
Scandit.Activity.onReady(function() {
    app.start();
});

src/views/view.html:

<p>{{ template_variable }}</p>

The code that was generated for your new activity will simply show "Hello World" on the screen. It instantiates a new App object as soon as the framework is ready and renders the view named src/views/view.html, passing it the string "Hello World" in the template_variable variable that is then accessed in the view.

To try out your new activity on a phone, you will first need to connect it with your Flow account. (Use the "Connect devices" option in the main navigation.) You then need to release your activity by clicking the "Publish All Files" button in the activity editor. Make sure that you release your activity to the same group (development, testing or production) that your device is assigned to. Once you have published your activity, it will become available on all connected devices after a few seconds. (The devices are notified by Flow with a push notification and will update their configuration immediately.)

Congratulations, you have just created your first activity! Now let's implement the functionality for our proof of delivery scenario. For the purpose of this tutorial, we will continue to use the web editor. Alternatively, you could also use your local text editor or IDE and publish the activity by pushing it to the Git repository that Flow has automatically created for you. If you want to learn more about how you can develop custom activities locally, read our Local Development Guide.

Step 2: Add main view

We will now add the main view to our activity. This is the view that lets the user scan barcodes and shows status information. We do so by creating a new view file named src/views/barcode.html with the following content:

<div class="row">
    <div class="six columns" style="border-right: 1px solid #D2D2D2;">
        <span class="label">Remaining packages:</span><br />
        {{remaining}}
    </div>
    <div class="six columns">
        <span class="label">Scanned packages:</span><br />
        {{completed}}
    </div>
</div>
<div class="row last">
    <button on-click="finalizeShipment()" class="button-primary">Shipment complete</button>
</div>

Also, we add some CSS code at the bottom of src/styles/default.css to make the view look a little nicer:

.row {
    text-align: center;
}

button, .button {
    border-radius: 0;
    display: block;
    width: 100%;
    margin: 15px auto;
    line-height: 60px;
    height: 60px;
    font-size: 18px;
}

.button.button-primary, button.button-primary, .button.button-primary:hover, button.button-primary:hover {
    background-color: #42C1CC;
    border-color: #42C1CC;
}

You will notice that src/styles/default.css already contains CSS code. Flow adds this code automatically to provide customizable default styling that comes in handy when you build an activity. We recommend that you keep the src/styles/default.css file, but can alternatively remove it and write your own CSS code from scratch.

To show our new view, we add a function named scanBarcodes in src/scripts/app.js:

this.scanBarcodes = function() {
    var barcodeView = Scandit.Ui.Views.load('src/views/barcode.html');
    var shipment = null;
};

You can ignore the shipment variable for now. We will need it later.

Finally, we replace the Scandit.Ui.Views.load('src/views/view.html', {template_variable: 'Hello World'}); line in the start function with scanBarcodes();.

Our src/scripts/app.js file now looks as follows:

function App(){

    this.start = function(){
        this.scanBarcodes();
    };

    this.scanBarcodes = function() {
        var barcodeView = Scandit.Ui.Views.load('src/views/barcode.html');
        var shipment = null;
    };

}

var app = new App();
Scandit.Activity.onReady(function() {
    app.start();
});

When you're done, publish your changes and test them on your phone.

Step 3: Add barcode scanning

Now let's add barcode scanning to our new view by inserting the following code at the end of the scanBarcodes function:

var scanner = new Scandit.BarcodeScanner({
    symbologies: ['UPC12', 'UPCE', 'EAN13', 'EAN8', 'CODE128'],
    size: 0.65,
    position: 'top',
    manualInputPlaceholder: 'Enter barcode manually'
});
scanner.show().scan({continuous: true});
scanner.on('scan', function(barcode) {
    alert(barcode);
});

This creates a new scanner that can scan UPC-12, UPC-E, EAN-13, EAN-8 and Code-128 barcodes. The scanner will be placed at the top of the view, cover 65% of the screen and have an optional input field where the user can type in the barcode manually. We then start the scanner in continuous mode (i.e., scanning continues once a barcode has been recognized) and attach a callback for the scan event that will simply show an alert with the barcode.

Publish your updates and test them on you phone. Here's a barcode that you can use for testing:

At this point, your src/scripts/app.js file looks as follows:

function App(){

    this.start = function(){
        this.scanBarcodes();
    };

    this.scanBarcodes = function() {
        var barcodeView = Scandit.Ui.Views.load('src/views/barcode.html');
        var shipment = null;
        var scanner = new Scandit.BarcodeScanner({
            symbologies: ['UPC12', 'UPCE', 'EAN13', 'EAN8', 'CODE128'],
            size: 0.65,
            position: 'top',
            manualInputPlaceholder: 'Enter barcode manually'
        });
        scanner.show().scan({continuous: true});
        scanner.on('scan', function(barcode) {
            alert(barcode);
        });
    };

}

var app = new App();
Scandit.Activity.onReady(function() {
    app.start();
});

If your activity does not work as expected, you can use the device log feature that is available from the "Connect devices" section. The device log will show the error messages from the JavaScript interpreter on your phone. You can also explicitly write to the device log by using console.log('some log message'); in your own JavaScript code.

Step 4: Handle the recognized barcode

We will now add some business logic to handle the recognized barcode. For the purpose of this tutorial, we assume that there is a Code-128 label on each package that is part of the shipment and that barcode encodes the following information:

  • Shipment number: A number that uniquely identifies the shipment.
  • Total packages: Total number of packages in the shipment.
  • Package number: The individual package's number (can be less than or equal to total packages).

These elements are encoded in a barcode by concatenating them and using a dash (-) as the delimiter. So for shipment with number 673892 and consisting of three packages we would have the following three barcodes (one per package):

Package 1 Package 2 Package 3
Barcode image
Encoded value 673892-3-1 673892-3-2 673892-3-3

We now add some logic that validates that the shipment is complete and no package is missing. We would like to show a confirmation message when all packages of the shipment have been scanned and an error message when a package from a different shipment is scanned. To do so, we replace the alert(barcode); line from the previous step with the following code:

if (!barcode.match(/^\d+-\d+-\d+$/)) {
    alert('Invalid barcode.');
    return;
}
var parts = barcode.split('-');
if (!shipment) {
    shipment = {
        number: parts[0],
        total: parseInt(parts[1]),
        scanned: []
    };
} else if (shipment.number != parts[0]) {
    alert('Package is not part of the same shipment.');
    return;
} else if (shipment.scanned.indexOf(barcode) != -1) {
    alert('Package was already scanned.');
    return;
}
shipment.scanned.push(barcode);
barcodeView.set({
    remaining: shipment.total - shipment.scanned.length,
    completed: shipment.scanned.length
});
if (barcodeView.get('remaining') === 0) {
    scanner.hide();
    alert('Shipment with number ' + shipment.number + ' and ' +
          shipment.total + ' packages completed.');
}

Step 5: Add callback and stubs for signature capture and DB access

Now that we have barcode scanning and our business logic in place, we will restructure our code a bit so we can easily add the remaining functionality afterwards.

Our current implementation of the scanBarcodes function simply shows an alert confirming that all packages have been scanned. Instead of this, we would like our function to invoke a callback function in which we will further process (e.g., write to the DB) the completed shipment. For this, we will need to modify the scanBarcodes function to accept a callback. We do this by changing its signature to this.scanBarcodes = function(done). We also replace the line that shows the confirmation alert ("Shipment with number ... and ... packages completed.") with the following line that invokes the callback: done(shipment.number, shipment.total, []);

As a next step, we add stubs for two new functions:

this.captureSignature = function(done) {
    done(null);
};

this.writeToDB = function(shipmentNumber, packageCount, missingPackages, signature, done) {
    done();
};

Just like scanBarcodes these two new functions accept a callback as the last argument (done). The callback function will get invoked when the user has signed on the phone's screen or when all data has been written to the database, respectively. In captureSignature, the signature (null in our stub) is also passed to the callback.

Finally, we chain these functions in start to make sure that the user is asked to sign when packages have been scanned and that the data is writen to the DB when signature has been captured:

this.start = function(){
    var self = this;
    self.scanBarcodes(function(shipmentNumber, packageCount, missingPackages) {
        self.captureSignature(function(signature) {
            self.writeToDB(shipmentNumber, packageCount, missingPackages, signature, function() {
                alert('Shipment with number ' + shipmentNumber + ' and ' + packageCount + ' packages completed.');
                self.start();
            });
        });
    });
};

Our src/scripts/app.js file now looks as follows:

function App(){

    this.start = function(){
        var self = this;
        self.scanBarcodes(function(shipmentNumber, packageCount, missingPackages) {
            self.captureSignature(function(signature) {
                self.writeToDB(shipmentNumber, packageCount, missingPackages, signature, function() {
                    alert('Shipment with number ' + shipmentNumber + ' and ' + packageCount + ' packages completed.');
                    self.start();
                });
            });
        });
    };

    this.scanBarcodes = function(done) {
        var barcodeView = Scandit.Ui.Views.load('src/views/barcode.html');
        var shipment = null;
        var scanner = new Scandit.BarcodeScanner({
            symbologies: ['UPC12', 'UPCE', 'EAN13', 'EAN8', 'CODE128'],
            size: 0.65,
            position: 'top',
            manualInputPlaceholder: 'Enter barcode manually'
        });
        scanner.show().scan({continuous: true});
        scanner.on('scan', function(barcode) {
            if (!barcode.match(/^\d+-\d+-\d+$/)) {
                alert('Invalid barcode.');
                return;
            }
            var parts = barcode.split('-');
            if (!shipment) {
                shipment = {
                    number: parts[0],
                    total: parseInt(parts[1]),
                    scanned: []
                };
            } else if (shipment.number != parts[0]) {
                alert('Package is not part of the same shipment.');
                return;
            } else if (shipment.scanned.indexOf(barcode) != -1) {
                alert('Package was already scanned.');
                return;
            }
            shipment.scanned.push(barcode);
            barcodeView.set({
                remaining: shipment.total - shipment.scanned.length,
                completed: shipment.scanned.length
            });
            if (barcodeView.get('remaining') === 0) {
                scanner.hide();
                done(shipment.number, shipment.total, []);
            }
        });
    };

    this.captureSignature = function(done) {
        done(null);
    };

    this.writeToDB = function(shipmentNumber, packageCount, missingPackages, signature, done) {
        done();
    };

}

var app = new App();
Scandit.Activity.onReady(function() {
    app.start();
});

If you try the code on your phone, you will notice that we have simply refactored our code and that the activity's behavior has not changed.

Step 6: Capture signature

Now that we already have a stub for the captureSignature function, it is easy to show a screen where the recipient can sign. Just replace the existing stub with the following code and try it on your phone:

this.captureSignature = function(done) {
    new Scandit.Signature().capture().then(function(signature) {
        done(signature);
    }).catch(function(reason) {
        done(null);
    });
};

In the above code, then and catch are used to specify which callback (anonymous functions in the code above) should be called when an asynchronous operation (capture in our code) completes. then is used to define the function that should be called when the operation (capture) completes successfully, while catch is used to define the function that should be called in case of an error. This construct is called a promise and is commonly used in JavaScript (see https://www.promisejs.org/ for details).

Step 7: Set up storage and write to database

We will now store the collected barcodes and the recipient's signature in a database. Flow comes with a built-in replicating database that is available on each phone, but also in the cloud. Having a database available on each phone has the advantage that your activity will not rely on network connectivity. You can read and write data even when the phone is offline. As soon as connectivity is restored, Flow will automatically synchronize updated objects.

Flow organizes data in classes. For our proof of delivery activity, we will need a Shipment class that lets us create an object holding shipment details such as scanned barcodes, missing packages, signature, etc. for each shipment.

To create the class, we need to log in to the Flow web dashboard, navigate to the "Storage" section, click "Add Class" and enter Shipment as class name. The class description is optional and can be used as a memo for administrators. Once we have created the class, we need to add the following fields:

  • timestamp (integer, required)
  • shipment_number (string, required)
  • total_pacakges (integer, required)
  • missing_packages (array[string], not required)

We finish the setup of our class by changing its data synchronization configuration from "Bidirectional" to "Mobile to cloud only". This will make sure that objects in the cloud are not replicated to phones and that objects on the phone are automatically removed as soon as they have been replicated to the cloud. We choose this setting because in our proof of delivery scenario we don't need the collected data on the mobile device any more after it has been synchronized to the central cloud database.

We now replace the existing stub for the writeToDB function with the following code:

this.writeToDB = function(shipmentNumber, packageCount, missingPackages, signature, done) {
    var shipment = new Scandit.Db.Shipment({
        timestamp: (new Date()).getTime(),
        shipment_number: shipmentNumber,
        total_packages: packageCount,
        missing_packages: missingPackages
    });
    shipment.save().then(function() {
        shipment.setAttachment(shipmentNumber + '-signature.png', signature, 'image/png');
        done();
    });
};

In the above code, we first create a new database object based on the Scandit.Db.Shipment class, which we set up before through the web dashboard. All database classes that you create via the dashboard become available automatically under the Scandit.Db namespace and implement the Scandit.Db.BaseModel API.

We then save the shipment object to the phone's local database from where it is replicated to the cloud automatically. If the object was saved successfully, we also add the image containing the signature as an attachment. In Flow, you use attachments for everything that would be a BLOB in a traditional relational database.

Try your updated activity with your phone, scan all three barcodes from above and sign on the phone's screen. If you then go to the "Storage" section in the web dashboard and click "Add/edit objects" under your Shipment class, you will see the details of the shipment. You can see the signature by simply clicking the PNG file in the "Attachments" column.

We can now use the "Export data" to download the collected data as a CSV file from the web dashboard and process it further. Another option is to access it via Flow's REST API, which we will show in the final step of this tutorial.

Step 8: Add event handler to button

In our proof of delivery scenario, there may be a situation where the user cannot scan all barcodes because a package is missing. For this case we already have the "Shipment Complete" button in our view. We will now add an event handler to it that will take the user to the next step even though not all items have been scanned.

At the end of the scanBarcodes function, add the following code:

barcodeView.finalizeShipment = function() {
    Scandit.Ui.Alert.show({
        title: 'Finalize shipment',
        text: 'You have not scanned all barcodes yet. Do you still want to finalize the shipment?',
        buttons: [
            {label: 'No'},
            {label: 'Yes', callback: function() {
                scanner.hide();
                var missingPackages = [];
                for (var i = 1; i <= shipment.total; i++) {
                    var itemBarcode = shipment.number + '-' + shipment.total + '-' + i;
                    if (shipment.scanned.indexOf(itemBarcode) == -1) missingPackages.push(itemBarcode);
                }
                done(shipment.number, shipment.scanned.length, missingPackages);
            }},
        ]
    });
};

The important thing to note in the above code snippet is that the first line defines the finalizeShipment function that will be called by the button that you have already created in your src/views/barcode.html file:

<button on-click="finalizeShipment()" class="button-primary">Shipment complete</button>

Try it on your phone. Tapping the "Shipment Complete" button will now let you proceed without scanning all packages.

Step 9: Download data via REST API

Flow offers a Platform REST API that can be used to access the database in the cloud. In this step, we will use the curl command line tool to fetch all collected shipments via this API.

To access the REST API, you will need a token. You need to first acquire it by making a simple POST request to the API, we will use curl to do this. You will need a Client ID and a Client Secret which you can generate in the "API Access" section of the web dashboard by creating a new OAuth application and choosing "API Key - client credentials". Substitute these (<CLIENT ID> and <CLIENT SECRET>) in the following command:

$ curl -d "grant_type=client_credentials&client_id=<CLIENT ID>&client_secret=<CLIENT SECRET>" https://scandium.scandit.com/api/v1/auth/oauth2/token

In the response, you'll get an access token which you can pass as the "Authorization" header, prefixed with bearer.

In your terminal or command prompt, type the following (make sure to replace <YOUR TOKEN> with your own access token):

$ curl -H "Authorization: bearer <YOUR TOKEN>" https://scandium.scandit.com/api/v1/storage/data/Shipment/

If you are on Windows, you may need to first download the curl tool.

You can now see a JSON document containing all Shipment objects:

{
  "metadata": {
    "page": 1,
    "pages": 1,
    "total": 1
  },
  "objects": [
    {
      "_id": "2be6c8f5-fd29-4dc4-a459-20256e14a6d9",
      "timestamp": "1455442937867",
      "total_packages": 3,
      "missing_packages": [

      ],
      "shipment_number": "673892",
      "attachments": [
        {
          "url": "https://scandium.scandit.com\/api\/v1\/storage\/data\/Shipment\/2be6c8f5-fd29-4dc4-a459-20256e14a6d9\/attachments\/673892-signature.png",
          "name": "673892-signature.png",
          "mime_type": "image\/png",
          "size": 8767
        }
      ]
    }
  ]
}

For a detailed description of all API methods, please refer to the Platform REST API documentation.

If you prefer to use a GUI tool instead of curl, you can also try Postman, for example.

Done

Congratulations! You have just created your first activity based on the Flow platform.

To wrap up this tutorial, we will show the entire code here again:

src/views/barcode.html:

<div class="row">
    <div class="six columns" style="border-right: 1px solid #D2D2D2;">
        <span class="label">Remaining:</span><br />
        {{remaining}}
    </div>
    <div class="six columns">
        <span class="label">Scanned:</span><br />
        {{completed}}
    </div>
</div>
<div class="row last">
    <button on-click="finalizeShipment()" class="button-primary">Shipment complete</button>
</div>

src/scripts/app.js:

function App(){

    this.start = function(){
        var self = this;
        self.scanBarcodes(function(shipmentNumber, packageCount, missingPackages) {
            self.captureSignature(function(signature) {
                self.writeToDB(shipmentNumber, packageCount, missingPackages, signature, function() {
                    alert('Shipment with number ' + shipmentNumber + ' and ' + packageCount + ' packages completed.');
                    self.start();
                });
            });
        });
    };

    this.scanBarcodes = function(done) {
        var barcodeView = Scandit.Ui.Views.load('src/views/barcode.html');
        var shipment = null;
        var scanner = new Scandit.BarcodeScanner({
            symbologies: ['UPC12', 'UPCE', 'EAN13', 'EAN8', 'CODE128'],
            size: 0.65,
            position: 'top',
            manualInputPlaceholder: 'Enter barcode manually'
        });
        scanner.show().scan({continuous: true});
        scanner.on('scan', function(barcode) {
            if (!barcode.match(/^\d+-\d+-\d+$/)) {
                alert('Invalid barcode.');
                return;
            }
            var parts = barcode.split('-');
            if (!shipment) {
                shipment = {
                    number: parts[0],
                    total: parseInt(parts[1]),
                    scanned: []
                };
            } else if (shipment.number != parts[0]) {
                alert('Package is not part of the same shipment.');
                return;
            } else if (shipment.scanned.indexOf(barcode) != -1) {
                alert('Package was already scanned.');
                return;
            }
            shipment.scanned.push(barcode);
            barcodeView.set({
                remaining: shipment.total - shipment.scanned.length,
                completed: shipment.scanned.length
            });
            if (barcodeView.get('remaining') === 0) {
                scanner.hide();
                done(shipment.number, shipment.total, []);
            }
        });
        barcodeView.finalizeShipment = function() {
            Scandit.Ui.Alert.show({
                title: 'Finalize shipment',
                text: 'You have not scanned all barcodes yet. Do you still want to finalize the shipment?',
                buttons: [
                    {label: 'No'},
                    {label: 'Yes', callback: function() {
                        scanner.hide();
                        var missingPackages = [];
                        for (var i = 1; i <= shipment.total; i++) {
                            var itemBarcode = shipment.number + '-' + shipment.total + '-' + i;
                            if (shipment.scanned.indexOf(itemBarcode) == -1) missingPackages.push(itemBarcode);
                        }
                        done(shipment.number, shipment.scanned.length, missingPackages);
                    }},
                ]
            });
        };
    };

    this.captureSignature = function(done) {
        new Scandit.Signature().capture().then(function(signature) {
            done(signature);
        }).catch(function(reason) {
            done(null);
        });
    };

    this.writeToDB = function(shipmentNumber, packageCount, missingPackages, signature, done) {
        var shipment = new Scandit.Db.Shipment({
            timestamp: (new Date()).getTime(),
            shipment_number: shipmentNumber,
            total_packages: packageCount,
            missing_packages: missingPackages
        });
        shipment.save().then(function() {
            shipment.setAttachment(shipmentNumber + '-signature.png', signature, 'image/png');
            done();
        });
    };

}

var app = new App();
Scandit.Activity.onReady(function() {
    app.start();
});