Over a million developers have joined DZone.

Building Mobile Applications To Be Proud Of

How to start building great web apps by using Javascript, the Ionic Framework, and Apache Cordova.

· Mobile Zone

Smart phones are everywhere, and if your company does not have mobile application out there, it is either developing it or planning around it.

With mobile applications you only have one chance to make a positive impression on the user. If it is a new business channel, first impressions will be the difference between creating and losing the revenue stream. Having polished UIs will get you good marks, however there are additional things to worry about: crashes, data loss or inconsistencies, cellular networks, hardware variability...

There are two approaches for mobile development nowadays: developing native code vs. developing hybrid application based on Javascript code and native plugins. With the second approach, the transition from web development to mobile development is easy. What one can miss in such transition is the fact that there has to be architectural paradigm shift.  

The gotchas discussed in the article are applicable to both methods of mobile application development. I will be referencing Ionic Framework (hybrid solution) built on top of Cordova for samples to make them simple for wider audience. Read through, think through, and put it to a good use.

Develop Offline First

There are no free lunches, and cellular data is definitely not one of them. The user of the application can easily block the data or be in the area with no connectivity. Further more, consuming too much of the bandwidth will trigger throttling and users will be left with very long upload/download times for everything.

Before creating robust wireframes of the user stories the application can do while connected, stop and think what will be the application value add for the user with no or limited connectivity. Build on-line user stories as a continuation of the offline mode. Think banking application vs. social application.

  • Should the user be able to access the app offline? Does the app require initial registration for further usage?

  • Should the user be able to enter new information through the app? How will offline changes be tracked?

  • Should the user be able to see old information through the app?

  • Once online, will the application or the user initiate the synchronization? 

  • What will happen if the application is online, but the server side is unavailable? 

These questions will get you started, keep asking similar questions and you will never disappoint your user.

How the app will know if its connected or not, and what kind of connection is it? Using cordova plugin network information that can be done as follows. Note isOnline method and navigator.connection.type - those will provide platform independent answers. The plugin documentation lists all the types that the plugin can report back.

$cordovaNetwork && navigator.connection && navigator.connection.type &&
          $cordovaNetwork.isOnline()

How can the app detect communication issues with its backend server? It's a matter of using standard check in error callback of $http requests. No native magic here. Please note that HTTP status Unauthorized (401) and HTTP status Forbidden (403) are excluded, as those are security and not communication related issue.

(status !== null && status !== undefined && (status === 0 ||
          (status >  400 && status !== 401 && status !== 403)))

As long as the application behavior reflects any connectivity changes right away and adjusts accordingly, the user will be happy.

Make Every Payload Count

Fine-grained requests are a sure recipe for drained battery. The only activity that drains battery more than radio is the screen. With the advances in mobile operating systems, users can spot offending apps right away. The solution is to minimize the traffic, and minimize the chatter. Questions to ask:

  • When should the data be sent over?

  • How can data be packaged to avoid consuming the bandwidth? The actual strategy is data dependent:

    • sending only the changes (the delta) over

    • compressing the data

Perfect Is the Enemy of Good

Talking about data compression: our amazing mobile devices are equipped with cameras and microphones that are getting better and better. As a result getting the data of high quality may result in files of significant size. The questions to ask

  • What is good enough?

  • What will be done with the image or the recording?

For instance, by default stereo is recorded with the standard headset microphone that is 2 channels, while physically only 1 channel is present. If the sound recorded is a voice message, it does not need the quality of musical recording. It will be enough that the voice is loud and clear. That impacts the recording sampling frequency. 1 channel will be sufficient for voice. Cordova plugin media capture does not allow that level of control, and I am offering iOS native code instead:

var recordSettings = [
        AVFormatIDKey: kAudioFormatMPEG4AAC ,
        AVEncoderAudioQualityKey : AVAudioQuality.Medium.rawValue,
        AVEncoderBitRateKey : 128000,
        AVNumberOfChannelsKey: 1, // 1 mic === mono
        AVSampleRateKey : 44100 
    ]

Mobile applications I work on capture images such as printed/written notes, product labeling, etc. Through empirical testing we came up with the size of the image pixel and size wise that works. In this case Cordova plugin camera was up to the task. The initial quality was set to 25 for iOS and 50 for Android. Each time a successful photo is taken, the app can check if the resulted image size falls within desired interval (128k...512k worked for us). If the size is outside of the interval and there is place for adjustment, an adjustment can be made +/- 10 points off the current quality. The quality should not go above 90, as this can cause the application to crash.

var options = {
            quality: angular.isDefined($scope.data.quality) ? parseInt($scope.data.quality) :
              DataService.imageQuality(),
            destinationType: Camera.DestinationType.DATA_URL,
            sourceType: source,
            // iOS clips the file if allowed to edit - EDIT only for Android
            allowEdit: ($cordovaDevice && $cordovaDevice.getPlatform && $cordovaDevice.getPlatform() !== 'iOS'),
            encodingType: Camera.EncodingType.JPEG,
            saveToPhotoAlbum: false,
            correctOrientation: true,
            targetWidth: 2592,
            targetHeight: 3872
          };

$cordovaCamera.getPicture(options).then(fnImageSuccess, fnImageError);

Don't Lose Data

Nothing is perfect and unfortunately things can happen. Don't make a wrong impression with the user by having him/her redo all the typing, picture taking, and voice recording in case of an unfortunate event. Store information locally, dispose of the information once not needed and be mindful of the storage space available. Mobile devices have their limitations.

There are multiple options for storage: application preferences, file system, embedded databases. Just make sure everything entered will not be lost.

Test, Test, Test 

If the line of business is mobile application development it can be justifiable to own various permutations of mobile devices to cover the supportability matrix (~thousands of devices). The cost of buying and owning the devices you need to support may be prohibitive. A stopgap solution can be simulators or services like Keynote. At a minimum, one needs access to one phone and one tablet for each platform the business plans to support.

Unit/integrations tests for hybrid applications can be done with Karma.

Keep an eye on the App Out in the Wild

Once the application is installed, it is on its own. Having an option for the user to call/email about a problem is good. Having the application alert you about any issues is great. Knowing what the user is doing with the app, and how app responds is golden. Google Analytics plugin is a useful tool. An instrumented application will send back errors, crashes, timings, and events. What's not to love?

// init
var analytics = (navigator && navigator.analytics) ? navigator.analytics : undefined;
        if (!analyticsInitialized && analytics) {
          analytics.setTrackingId(ENV.analyticsId);
          analytics.setLogLevel(analytics.LogLevel.INFO);
          analytics.trackUnhandledScriptErrors();
          analyticsInitialized = true;
        }
// send exception
var analytics = (navigator && navigator.analytics) ? navigator.analytics : undefined;
if (angular.isDefined(analytics)) {
          analytics.sendException(
            ((typeof message === 'object') && message) ? JSON.stringify(message):
              (typeof message === 'string') ? message : 'N/A',
            fatal? fatal : false, analyticsSuccessCallback, analyticsErrorCallback);
     }

// send event
if (angular.isDefined(analytics)) {
          analytics.sendEvent(category, action, label, value, analyticsSuccessCallback, analyticsErrorCallback);
     }

// send timing
if (angular.isDefined(analytics)) {
          analytics.sendTiming(category, variable, label, time, analyticsSuccessCallback, analyticsErrorCallback);
    }


So far we covered a lot of ground. If you feel you want to go deeper, there are many tutorials for hybrid or native applications that will get you going. Also, if you know what you are looking for, you will be able to find an example out on the web.

I would love to hear back about additional gotchas not covered, corrections, clarification, or if the information was helpful. 

Irena

Many thanks to my friend Sarah for editing the article and making the message sharp and clear.

Image created by Nick Abrams for Noun Project

Topics:
mobile app development

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}