Adding Native Touches to Your Hybrid App

Designing hybrid apps can be difficult. The challenge is to use HTML, CSS, and JavaScript to build apps that feel at home on multiple mobile platforms with different UI paradigms. As hybrid developers we often argue over the merit of replicating native UIs in JavaScript, and toss around phrases like the uncanny valley effect, but we frequently overlook the fact that Cordova gives us access to native OS UI components directly.

For example, popular hybrid UI frameworks Ionic and Kendo UI Mobile both have ActionSheet controls—here’s Ionic’s and here’s Kendo UI Mobile’s—but I’m not sure why you would use either, as both are subject to the uncanny valley effect (aka they look “off” to the user), and there’s a Cordova plugin that makes it dead simple to use the native control directly.

Using native UI elements has all sorts of benefits: you give users a UI they’re familiar with, you avoid building (and shipping) unnecessary JavaScript-based widgets, and you get a UI that’s consistent with the OS the app is running on. If nothing else you score a few bonus points during Apple’s review process. In this article you’ll learn about a few Cordova plugins that are easy to drop in, and how to integrate them into your workflow.

This article assumes you are using either the Cordova CLI or AppBuilder CLI to install Cordova plugins. Here’s a guide to getting started with the Cordova CLI, and here’s one for AppBuilder.

Dialogs

Installation: cordova plugin add org.apache.cordova.dialogs or appbuilder plugin fetch org.apache.cordova.dialogs
Documentation: https://github.com/apache/cordova-plugin-dialogs/blob/master/README.md

The easiest types of elements to add to your app are dialogs, as they don’t live in your main interface, but they can serve as prompts, confirmation dialogs, and more. The Cordova dialog plugin provides three types of dialogs: alert, prompt, and confirm. Let’s look at how each work.

Alerts

An alert is the simplest type of native interaction you can add to your Cordova app, as it’s a method call with a very straightforward API. For example the following code shows a native alert message:

navigator.notification.alert(
    "Whassssssssuuuuupppp",             // the message
    function() {},                      // a callback
    "My Very Professional Application", // a title
    "OK"                                // the button text
);

Here’s what that alert looks like on my (left to right) Android, iOS, and Windows Phone devices:

A Cordova dialog alert

Prompts

Alerts are nice for simple notifications, but you can’t gather user input from them. That’s where the next two types of dialogs come in. The first, prompt, presents the user with a text input to enter data in. For example the following code asks the user for a name:

navigator.notification.prompt(
    "Please give this order a name", // the message
    function( index ) {
        switch ( index ) {
            case 1:
                // The first button was pressed
                break;
            case 2:
                // The second button was pressed
                break;

        }
    },
    "Coffee World",     // a title
    [ "Ok", "Cancel" ], // text of the buttons
    "My order"          // the default text
);

Here’s what the prompt looks like on the same three mobile devices:

A Cordova dialog prompt asking the user for an order name

Prompts are great when you need to grab a string from the user without having to build a JavaScript-based control yourself.

Confirmation Dialogs

The final type of dialog, confirm, is the one I use the most frequently. Most apps need to confirm a user’s action or present the user with a set of options, and the confirm dialog makes that UI easy to build. For example the following asks the user a yes/no question:

navigator.notification.confirm(
    "Delete the *whole* internet?", // the message
    function( index ) {
        switch ( index ) {
            case 1:
                // The first button was pressed
                break;
            case 2:
                // The second button was pressed
                break;
        }
    },
    "NSA Admin Panel", // a title
    [ "Yes", "No" ]    // text of the buttons
);

Here’s what that UI looks like:

A Cordova confirmation dialog

The confirm() method also intelligently handles dialogs that use three buttons, as the following code uses:

navigator.notification.confirm(
    "Could you take a minute to rate my app?", // the message
    function( index ) {
        switch ( index ) {
            case 1:
                // The first button was pressed
                break;
            case 2:
                // The second button was pressed
                break;
            case 3:
                // The third button was pressed
                break;
        }
    },
    "Desperate for reviews",                   // a title
    [ "Sure", "Remind me later", "NO! STOP!" ] // text of the buttons
);

Here’s how my three devices display this three-button dialog:

A Cordova confirmation dialog with three buttons

Overall, dialogs are a nice native touch you can easily add to any hybrid app. The next time you need to show a confirmation dialog, consider using a native approach rather than a custom JavaScript widget.

ActionSheets

Installation: cordova plugin add https://github.com/Telerik-Verified-Plugins/ActionSheet or appbuilder plugin fetch https://github.com/Telerik-Verified-Plugins/ActionSheet
Documentation: http://plugins.telerik.com/plugin/actionsheet

Like dialogs, ActionSheets are convenient elements to add to hybrid apps because they don’t exist in the main layout. They’re actually quite similar to dialogs, as they act as popups that present the user with options.

Once installed, the ActionSheet API is a simple show() method available at window.plugins.actionsheet.show(). For its simplest usage, pass show() an object with two properties—buttonLabels and title—as well as a callback function. Here’s an example:

window.plugins.actionsheet.show({
    buttonLabels: [ "Delete it", "Email it", "Tweet it" ],
    title: "What would you like to do with this image?"
}, function( buttonIndex ) {
    // buttonIndex == 1 if the user chose "Delete it"
    // buttonIndex == 2 if the user chose "Email it"
    // buttonIndex == 3 if the user chose "Tweet it"
});

And here’s a screenshot that shows how the buttonLabels and title properties render on real devices:

A basic Cordova ActionSheet control

The plugin contains more advanced options for additional configuration. For example the addDestructiveButtonWithLabel adds buttons with red text on iOS/Windows Phone; the addCancelButtonWithLabel option adds a cancel button for iOS; and the androidEnableCancelButton/winphoneEnableCancelButton options enable the cancel button for Android and Windows Phone, respectively. Here’s an example that shows all of these options in action:

window.plugins.actionsheet.show({
    buttonLabels: [ "Email it", "Tweet it" ],
    title: "What would you like to do with this image?",
    addDestructiveButtonWithLabel: "Delete it",
    addCancelButtonWithLabel: "Cancel",
    androidEnableCancelButton: true,
    winphoneEnableCancelButton: true
}, function( buttonIndex ) {
    // buttonIndex == 1 if the user chose "Delete it"
    // buttonIndex == 2 if the user chose "Email it"
    // buttonIndex == 3 if the user chose "Tweet it"
});

And here’s how this control renders:

A Cordova ActionSheet using advanced options to control its display

In general, the ActionSheet is a nice control to use anytime you need to present the user with a list of actions they can take. For instance, the example above lets the user take various actions on an image. To show off another cool Cordova plugin, let’s see how you can extend this example with sharing functionality.

SocialSharing

Installation: cordova plugin add https://github.com/Telerik-Verified-Plugins/SocialSharing or appbuilder plugin fetch https://github.com/Telerik-Verified-Plugins/SocialSharing
Documentation: http://plugins.telerik.com/plugin/socialsharing

As its name implies, the SocialSharing plugin lets you share a variety of things— text, files, URLs, and so forth — using the native sharing widget on your device. The easiest way to use the SocialSharing plugin is to call its share() method, which is available at window.plugins.socialsharing.share(). For example consider the following:

window.plugins.socialsharing.share( "Hello World" );

This lets the user share a message (“Hello World”) using the social services already configured on their device. Here’s what that looks like:

The default native sharing widgets on Android, iOS, and Windows Phone

If you wish to share with a specific service directly, the plugin has shareViaEmail(), shareViaTwitter(), shareViaFacebook(), shareViaWhatsApp(), and shareViaSMS() methods (although note that only shareViaEmail() is available for Windows Phone). In the code below I extend our previous image example to allow users to use the shareViaEmail() method:

window.plugins.actionsheet.show({
    buttonLabels: [ "Email it", "Tweet it" ],
    title: "What would you like to do with this image?",
    addDestructiveButtonWithLabel: "Delete it",
    addCancelButtonWithLabel: "Cancel",
    androidEnableCancelButton: true,
    winphoneEnableCancelButton: true
}, function( buttonIndex ) {
    switch ( buttonIndex ) {
        case 2:
            window.plugins.socialsharing.shareViaEmail(
                "Because your inbox wasn't full enough", // message
                "Meme of the day", // subject
                [ "tj.vantoll@telerik.com" ], // to addresses
                null, // cc addresses,
                null, // bcc addresses
                [ "http://cdn.meme.am/instances/300x300/55260957.jpg" ], // files
                function() {}, // success callback
                function() {} // error callback
            );
            break;
    }
});

In the image below I run this code and select the “Email It” option on my three devices. Notice how each composes an email for me with the image attached.

Sharing an image over email using the SocialSharing plugin

Next let’s implement the “Tweet it” button with the shareViaTwitter() method. Note that because shareViaTwitter() is not supported on Windows Phone, I open Twitter in an in-app browser.

window.plugins.actionsheet.show({
    buttonLabels: [ "Email it", "Tweet it" ],
    title: "What would you like to do with this image?",
    addDestructiveButtonWithLabel: "Delete it",
    addCancelButtonWithLabel: "Cancel",
    androidEnableCancelButton: true,
    winphoneEnableCancelButton: true
}, function( buttonIndex ) {
    var url = "http://cdn.meme.am/instances/300x300/55260957.jpg";
    switch ( buttonIndex ) {
        case 2:
            window.plugins.socialsharing.shareViaEmail(...);
            break;
        case 3:
            // The SocialSharing plugin's shareViaTwitter method does not currently support Windows Phone
            if ( /^Win/.test( device.platform ) ) {
                window.open( "http://twitter.com/share?text=My%20awesome%20meme&url=" + url, "_blank" );
                return;
            }
            window.plugins.socialsharing.shareViaTwitter(
                "My awesome meme", // message
                url, // file
                null, // url
                function() {}, // success callback
                function() {} // error callback
            );
            break;
    }
});

In the gif below I show the new functionality of the “Tweet it” button on my devices:

Implementation of Twitter sharing using the SocialSharing plugin

What’s cool about this approach is that you’re using the device’s native sharing functionality. Not only does this make your app feel more like a native app, it also saves you the trouble of importing web-based sharing solutions, such as tweet and like buttons.

Let’s look at one more cool plugin: native page transitions

Native Page Transitions

Installation: cordova plugin add appbuilder plugin fetch https://github.com/Telerik-Verified-Plugins/NativePageTransitions or appbuilder plugin fetch appbuilder plugin fetch https://github.com/Telerik-Verified-Plugins/NativePageTransitions
Documentation: http://plugins.telerik.com/plugin/native-page-transitions

Page transitions are one of the cornerstone features of any mobile UI framework. Ionic has them; jQuery Mobile has them; Onsen has them; and Kendo UI Mobile has them too. Tons of research has gone into making these transitions smooth and crisp on these devices, but no web-based transition is going to match the ones natively available on each mobile OS.

The Native Page Transitions plugin makes it simple to tie into these transitions with a simple JavaScript API. All you need to do is call window.plugins.nativepagetransitions.slide() or window.plugins.nativepagetransitions.flip() with a few simple arguments. In the image below I show what slide() and flip() look like on an iOS device:

The slide and flip transitions implemented using the Native Page Transitions plugin

Cool, right? But notice that in this gif I’m not actually transitioning to anything. That is, at the end of the transition I’m on the same page I started on. Actually updating the view is where the real magic of the Native Page Transitions plugin happens.

When you call one of the plugin’s transition methods, the plugin takes a screenshot of the app, waits a bit, and then performs the transition by animating out the screenshot and in the new view. You can pass any of the plugin’s methods an href property and it’ll take care of timing for you. For example, suppose I have a Kendo UI Mobile app with a view available at #play. I can perform a native transition to that view using the code below:

window.plugins.nativepagetransitions.slide({ href: "#play" });

For fun, and to show off the how this API works, this code continuously transitions between two views (available at #play and #info) every two seconds:

setInterval(function() {
    if ( window.location.hash === "#info" ) {
        window.plugins.nativepagetransitions.slide({ href: "#play" });
    } else {
        window.plugins.nativepagetransitions.flip({ href: "#info" });
    }
}, 2000 );

Here’s what these transitions look like on my iOS device:

Using the native flip and slide animations to transition between two views

Smooth, right? There are a bunch of additional configuration options for the Native Page Transitions plugin, such as configuring the direction and delay of these effects, but I find the default to be quite nice. If you develop hybrid apps you owe it to yourself to give this plugin a shot.

Hopefully at this point you’ve seen the power that utilizing Cordova plugins in your UIs can provide, but all this discussion of custom Cordova plugins begs one big question.

How do you test these things?

One reason Cordova developers have traditionally shied away from custom plugins is the difficulty of testing them. Web developers are comfortable in the browser and like to stick with workflows they know and love. It’s one of the reasons that workflows such as ionic serve, which lets you do Ionic development in a desktop web browser, are so popular.

But by fully embracing hybrid development, you unlock a whole new world of opportunities, such as the custom plugins introduced in this article. And with modern tooling, mobile device testing has never been easier. With AppBuilder you can deploy your apps to USB-connected Android and iOS devices with simple commands:

$ appbuilder deploy ios
$ appbuilder deploy android

Ok, ok, there is one big caveat. For iOS builds to work, you need to become a registered iOS developer ($99/year), create provisioning profiles in the iOS Dev Center, and import them into AppBuilder. Unfortunately, AppBuilder (or any other service) can’t remove any of these requirements as Apple provides zero APIs for this sort of thing. AppBuilder does provide comprehensive documentation to walk you through the process. If you plan on publishing to the App Store you’ll have to go through this process eventually anyway.

Once you have the apps on your devices, seeing changes is as easy as running appbuilder livesync. Append the --watch option and you your apps are instantly updated as you make changes.

$ appbuilder livesync ios --watch
$ appbuilder livesync android --watch

Using AppBuilder LiveSync to see changes render instantly

What’s cool is that LiveSync also works with Cordova plugins. Here I play with a Cordova dialog:

Using AppBuilder LiveSync to play with Cordova alerts

LiveSync also works with custom Cordova plugins. Here I play with the Cordova StatusBar plugin on my iOS device:

Using AppBuilder LiveSync to play with the iOS status bar

And when you’re testing on real devices you can still do the remote debugging that you’re used to on Safari and Chrome.

Remote debugging a Cordova app on Chrome and Safari

No longer does a lack of tools need to keep you from using Cordova plugins. Modern tools such as AppBuilder make it easy to add plugins, test changes, and maintain access to the native remote debugging tools.

Wrapping Up

By leveraging natively-available UI elements, you can make a hybrid app feel at home in a native world. Plugins like dialog, ActionSheet, SocialSharing, and Native Page Transitions are a good place to start as they’re easy to add to any existing hybrid app. Although using custom Cordova plugins has traditionally been painful, modern tools make it simple to add plugins and integrate them into your existing workflows.

Header image courtesy of Brandon Carson

Comments

  • After the introduction of Material Design, there is no excuse for preaching Gingerbread UI any longer. >=K

  • amir khan

    Hi!! i am beginner in phonegap.And I need to know about how to get the text from navigator.notification.prompt?

  • amir khan

    Hi!! i am beginner in phonegap.And I need to know about how to get the text from navigator.notification.prompt?

  • Daniela Ferrai

    Great article TJ!

    I have a question regarding the native transition plugin. I have a one page cordova app that uses a slightly different HTML structure than JQuery Mobile. Instead of using data-role and div to specify pages, headers, etc, I’m using HTML5 tags such as section, header, footer etc. Is there anyway I can make the plugin work without changing my page structure?

    Thanks in advance for your help.