Skip to main content

Building Mobile Applications with Gulp

Apache Cordova can be used to compile HTML5 applications for iOS and Android. This technique allows a developer to easily deploy a static web app to multiple mobile devices. For many genres of application its a perfectly acceptable strategy, especially with javascript performance getting better and better. Personally I want to have cross platform deployment while avoiding the hassle of programming in ObjectiveC for iOS as well as Android’s dialect of Java.
AngularJS is a widely popular javascript framework for all sorts of interactive web apps. Including my own Party Sense project. It is very well suited for developing static website based mobile applications. Because angularjs is used all over the web it doesn’t have much specific support for mobile development, it can however be used to build up common mobile user interactions for example things like tabs, swipe cards etc.
To avoid having to start from scratch ionicframework bridges the gap between cordova and angularjs. Ionic is essentially a mobile-optimized library of HTML, CSS and JS components for building interactive apps with angularjs. The mentality seems very similar to the Python development world:
Develop once, deploy everywhere.
Although you can just throw your code straight into the www directory and run cordova build platform; I like to maintain a working development version of the mobile app and then compile/minimize/concatenate the source into the final product. The main tool I use for building web applications is gulp.js - the streaming build system for node.
Gulp like most build systems has the concept of tasks and dependencies. Here is an example task which depends on the bump and tag tasks:
gulp.task('release', ['bump', 'tag'], function () {
    console.log('Installing latest stable release of dependencies from bower');
    bower.commands.install();
});
Across a number of projects I have a few common tasks.

Minify and Concat

This task is the most important, it rewrites css and javascript paths found within html, minify and uglify things. Inside my entry point html file I have notation like the following:
<!-- build:css css/style.css -->
<link rel="stylesheet" href="css/stylesheet.css"/>
<!-- endbuild -->
This allows me to have my verbosely commented development css stylesheets split across multiple files and the build system will take the entire contents tagged with build:css and after any processing put the output into css/style.css. The processing I do uses the incredible gulp-usemin plugin in this task:
gulp.task('usemin', function(){
    gulp.src('app/*.html')
        .pipe(usemin({
            css: [minifyCss(), 'concat', rev()],
            html: [minifyHTML({empty: true})],
            vendorjs: [uglify(), rev()],
            js: [jshint.reporter('default'), uglify(), rev()]
        }))
        .pipe(gulp.dest('www/'));
});
Running this task copies any top level html documents across to my output directory and rewrites any js/css paths. The innocuous command rev() puts a unique revision on the generated javascript file to avoid all kinds of cache pain.
Compression and minification can act much better on the entire app, I usually aim to have no more than three javascript files. The first is the ionic.bundle.min.js which includes Ionic, AngularJS, and a few other angular modules. The second is vendor.js which is a squashed and concatenated file containing all the project dependencies. For example in one application I use underscore and jsbn, so the index.html file has the following section:
<!-- build:vendorjs vendor.js -->
<script src="lib/underscore/underscore.js"></script>
<script src="lib/jsbn/jsbn.js"></script>
<script src="lib/jsbn/jsbn2.js"></script>
<!-- endbuild -->
These unminified javascript libraries will get squashed together into something like vendor-4f7118c3.js, and the resulting index.html file will contain <script src=vendor-4f7118c3.js></script>.
I do the same type of thing for my application code, but this code also gets passed through jshint to let me know if I’m doing something wrong!
<!-- My Application Code -->
<!-- build:js app.js -->
<script src="js/app.js"></script>
<script src="js/filters.js"></script>
<script src="js/controllers.js"></script>
<script src="js/services/services.js"></script>
<script src="js/directives.js"></script>
<!-- endbuild -->

Angular Templates

During development there is no problem with creating a network request for every template, but it would add unnecessary overhead for a deployed app or website. The next task takes a folder of angular html templates and combines and compresses them. I add a 'templates' dependency to my main app:
var app = angular.module('myapp', [ ... , 'templates']);
Which would result in an error running in development mode, so I create a dummy app/templates.js file:
angular.module('templates', [])
.run(function($log){
    $log.info("Running in Development Mode");
});
The gulp templates task then creates a new templates.js file which contains all information for all templates in a much smaller single request.
gulp.task('templates', function () {
    gulp.src(['./app/templates/*.html'])
        .pipe(minifyHTML({ quotes: true }))
        .pipe(templates({filename: 'templates.js', root: 'templates', standalone: true}))
        .pipe(size({title: 'HTML fragments'}))
        .pipe(gulp.dest('www/'));
});

Images

Often full resolution images are too large, I use the following task to set a maximum width and optimize all my images. As image resizing and compression can take an appreciable time I only run when changed.
gulp.task('images', function () {
    "use strict";
    var DEST = 'www/img';
    gulp.src(paths.images)
        .pipe(changed(DEST))
        .pipe(imageResize({width: 1080}))
        .pipe(imagemin({optimizationLevel: 4, progressive: true, interlace: true}))
        .pipe(gulp.dest(DEST));
});

sass

Probably goes without saying but ionic uses sass and after tweaking any of their style you have to run a sass preprocessor.
gulp.task('sass', function (done) {
    gulp.src(paths.sass)
        .pipe(sass())
        .pipe(gulp.dest('./app/css/'))
        .pipe(minifyCss())
        .pipe(rename({ extname: '.min.css' }))
        .pipe(gulp.dest('./www/css/'))
        .on('end', done);
});

gulp.task('watch', function() {
  gulp.watch(paths.sass, ['sass']);
});

Library Copy

I seem to often end up with a copy operation for things like fonts and javascript maps. I usually define all the paths and copy them from app to www with this task:
gulp.task('libcopy', function () {
    // the base option sets the relative root for the set of files,
    // preserving the folder structure
    gulp.src(paths.libs, {base: 'app'})
        .pipe(gulp.dest('www/'));

    gulp.src('app/data/**/*', {base: 'app'})
        .pipe(gulp.dest('www/'));
});

Cordova Icon

cordova-icon is a node script for generating iOS and Android icons. I have a pre-build hook that cordova calls before building for iOS or Android.
Resize icon for all targets

Comments

  1. Really means and inspires a lot to hear from you guys.I have bookmarked it and I am looking forward to reading new articles. Keep up the good work..Believe me, This is very helpful for me.
    Mobile App Development Company in Dubai
    Android App Development Company in Dubai
    Mobile App Development Company in UAE

    ReplyDelete

Post a Comment

Popular posts from this blog

Matplotlib in Django

The official django tutorial is very good, it stops short of displaying
data with matplotlib - which could be very handy for dsp or automated
testing. This is an extension to the tutorial. So first you must do the
official tutorial!
Complete the tutorial (as of writing this up to part 4).

Adding an image to a view

To start with we will take a static image from the hard drive and
display it on the polls index page.
Usually if it really is a static image this would be managed by the
webserver eg apache. For introduction purposes we will get django to
serve the static image. To do this we first need to change the
template.



Change the template
At the moment poll_list.html probably looks something like this:


<h1>Django test app - Polls</h1> {% if object_list %} <ul> {% for object in object_list %} <li><a href="/polls/{{object.id}}">{{ object.question }}</a></li> {% endfor %} </ul> {% else %} <p>No polls are available.</p> …

Homomorphic encryption using RSA

I recently had cause to briefly look into Homomorphic Encryption, the process of carrying out computations on encrypted data. This technique allows for privacy preserving computation. Fully homomorphic encryption (FHE) allows both addition and multiplication, but is (currently) impractically slow.

Partially homomorphic encryption just has to meet one of these criteria and can be much more efficient.
An unintended, but well-known, malleability in the common RSA algorithm means that the multiplication of ciphertexts is equal to the multiplication of the original messages. So unpadded RSA is a partially homomorphic encryption system.

RSA is beautiful in how simple it is. See wikipedia to see how to generate the public (e, m) and private keys (d, m).

Given a message x it is encrypted with the public keys it to get the ciphertext C(x)with:

C(x)=xemodm
To decrypt a ciphertext

Bluetooth with Python 3.3

Since about version 3.3 Python supports Bluetooth sockets natively. To put this to the test I got hold of an iRacer from sparkfun. To send to New Zealand the cost was $60. The toy has an on-board Bluetooth radio that supports the RFCOMM transport protocol.



The drive protocol is dead easy, you send single byte instructions when a direction or speed change is required. The bytes are broken into two nibbles: 0xXY where X is the direction and Y is the speed. For example the byte 0x16 means forwards at mid-speed. I was surprised to note the car continues carrying out the last given demand!

I let pairing get dealt with by the operating system. The code to create a Car object that is drivable over Bluetooth is very straight forward in pure Python:

importsocketimporttimeclassBluetoothCar:def__init__(self,mac_address="00:12:05:09:98:36"):self.socket=socket.socket(socket.AF_BLUETOOTH,socket.SOCK_STREAM,socket.BTPROTO_RFCOMM)self.socket.connect((mac_address,1))def_write(self,data_byte):…