April 2016

Volume 31 Number 4

[Mobile Apps]

Using Ionic and TACO to Create Cross-Platform Mobile Apps

By Adam Tuliper | April 2016

There are many workflows available for developing and deploying cross-platform applications, which means it’s not always easy to choose. Not only can you get stuck on what devel­opment technology to use, but the entire dev-to-production workflow is even more confusing. You can develop natively on each mobile platform, which requires you to be pretty skilled with C#, Objective-C/Swift, Java and sometimes C++. Or you might choose cross-platform tools like Apache Cordova or Xamarin. Beta testing is yet another issue, as you can side-load apps (painful beyond a couple users), use platform-specific beta testing distribution solutions, such as using promotional codes on Windows 10 devices, TestFlight on iOS, groups on Google Play, or platforms like HockeyApp. There are many ways to achieve a similar end result, but through very different journeys.

In this article, I’ll be taking a look at a workflow for creating cross-platform apps that includes tools installation, development, debugging, deployment (production or beta) and updates. This workflow utilizes the Ionic HTML5 mobile app framework, the Tools for Apache Cordova (TACO) command-line tools, CodePush, and Visual Studio Code, and it can integrate tightly into Visual Studio. I’ll focus on the command line and on the Visual Studio Code workflow, as these are essentially the same across Mac OS, Linux, and Windows using the same tools. You’ll definitely want the tooling for either Visual Studio Code or Visual Studio—it’s the best on the market for developing and debugging Cordova-based apps, though I want to stress the projects are completely interoperable among OSes (Mac OS, Linux, Windows), command lines (Ionic, Cordova and so forth), and tooling (Visual Studio Code or Visual Studio). The tooling for Visual Studio installs all of the required SDKs automatically and sets you up for Windows native and Android debugging.

If you’re a Web developer looking to do cross-platform app development and are new to Apache Cordova, I recommend getting started with the article, “Write Cross-Platform Hybrid Apps in Visual Studio with Apache Cordova” (msdn.com/magazine/dn879349), to get an overview of how the framework works.

Tools for Apache Cordova (TACO)

TACO is a suite of products designed to make development faster and more productive. There are multiple endpoints when developing an Apache Cordova application. The Visual Studio path installs all the required SDK and third-party tools, including an advanced emulator for Android. If you’re working on cross-platform development, a useful Visual Studio Code extension gives you features like IntelliSense and debugging from within Visual Studio Code. There’s also a set of command-line utilities called TACO-CLI that you can use for installing third-party SDKs if you prefer the command-­line path. These tools work with any Cordova-compatible project (Ionic, PhoneGap and more). I’m going to install TACO-CLI now so it’s available later when I need it:

#install taco one time (**this requires Node to be installed **)
npm install taco-cli –g

Whenever you add a platform to an Apache Cordova application via any of the following commands (which basically do the same thing depending on which command line you’re using), you’re only configuring the project for that platform; the command doesn’t do anything about checking for or installing the required SDKs:

cordova platform add android
#or
ionic platform add android
#or
taco platform add android

The Android platform, for example, requires dependencies like the Android SDK, Gradle, and Java, which aren’t added by the Ionic or Cordova command-line tools, but can be added from inside an existing project by running:

taco install-reqs android

This will install Java and the Android SDK, along with Gradle integration and Android Debug Bridge (adb) support.

Developing and Debugging

If you’re working cross-platform, Visual Studio Code has tooling that enables you to debug from an emulator or right on a device, as shown in Figure 1. All you do is right-click on a folder and select to open with Visual Studio Code, or use File | Open Folder. You can do this with any cross-platform Cordova-based application; there’s nothing Visual Studio-specific about this step and it allows you to make the workflow your own, quite a nice feature when you’re working with a team using other tools and platforms.

Debugging from Within Visual Studio Code
Figure 1 Debugging from Within Visual Studio Code

All you need to do to install this extension is to bring up the Command Palette via control-shift-p and type:

ext install cordova

To debug your app, simply switch to the Debug view (via the icon or Control+Shift+D) and choose your debugging target (as in Figure 1) and click play. This assumes you’ve already installed the platform-specific SDKs either manually or via taco install-reqs.

If you love Visual Studio, you can also work with existing project folders, such as those from command-line tools, by simply performing File | New | Project From Existing Code. Note that this only adds a .jsproj and taco.json file in your folder—that’s it. You can then happily use these from Visual Studio while your Mac OS and Linux developers share the same repository and use Visual Studio Code, the command line, and so forth. It all works well together. Of course, you can create a new Apache Cordova project, as well, from File | New Project and that will also work cross-platform. It’s a harmonious world, isn’t it?

What about IntelliSense? In Visual Studio you can install the Ionic Pack from the Extension Gallery right in Visual Studio or from bit.ly/1Vq4dIo. You get snippets, validation and IntelliSense. Visual Studio Code contains IntelliSense for Ionic once you install the Cordova Tools Extension.

The Ionic Framework

Ionic is a CSS framework and a JavaScript UI library, optionally coupled with a build system. It typically runs on top of Apache Cordova, the open source cross-platform HTML/JS/CSS native app framework. You generally hear of Ionic being used with Cordova, but that isn’t necessary; for example, check out some of the code pens at CodePen.io/ionic; Ionic is just HTML, CSS, JavaScript and images.

One of the challenges with creating convincing mobile apps is ensuring that they look and perform according to the design guidelines for each OS: the Material Design guidelines for Android; the UX Guidelines for Universal Windows Platform (UWP) apps and the Human Interface Guidelines for iOS. Whether you’re developing line-of-business apps or games, users have expectations regarding how an interaction should behave. Whether you realize it, there are micro-interactions you use daily throughout the devices, appliances and other items in your life.

Micro-interactions in a mobile app can be as simple as swiping the screen in a particular direction to perform an action, pulling to refresh, “liking” on a social media page or displaying a spinner. How do you handle these in a platform-specific manner? Ionic takes care of some of these details automatically. For example, it will detect if you’re on iOS or Android and use the appropriate CSS-styled spinner to look like the platform-specific one, as shown in Figure 2. Ionic tags for a spinner are custom HTML tags that look like the following:

 

<ion-spinner></ion-spinner>

Android and iOS Spinners
Figure 2 Android and iOS Spinners

Figure 3 shows example code for a native-looking list using just HTML and CSS in Ionic. This in turn yields the nice, native-looking top section of the app shown in Figure 4.

Figure 3 A Native-Looking List Created in Ionic Using HTML

<div class="list">
  <a class="item item-icon-left" href="#">
    <i class="icon ion-email"></i>
    Check mail
  </a>
  <a class="item item-icon-left item-icon-right" href="#">
    <i class="icon ion-chatbubble-working"></i>
    Call Ma
    <i class="icon ion-ios-telephone-outline"></i>
  </a>
  <a class="item item-icon-left" href="#">
    <i class="icon ion-mic-a"></i>
    Record album
    <span class="item-note">
      Grammy
    </span>
  </a>
  <a class="item item-icon-left" href="#">
    <i class="icon ion-person-stalker"></i>
    Friends
    <span class="badge badge-assertive">0</span>
  </a>
</div>

A Native-Looking List Created in Ionic
Figure 4 A Native-Looking List Created in Ionic

Getting Started

An Ionic application is just a Web page packaged locally in a Cordova app. There are custom HTML tags as you can see, as well as platform-­specific CSS styling. Ionic apps are built on top of Angular. Let’s create a simple Ionic app and explore its structure. If you’re familiar with Angular, this will be a breeze. If not, it’s pretty easy to understand what’s going on from a high level.  To use the Ionic command-line interface (cli), you should have npm installed (available when you install Node.js from Nodejs.org) and Git from Git-scm.com.

If you just want to use Ionic in a standalone app, you could simply install it via bower:

bower install ionic

Visual Studio users can take advantage of the Ionic template (managed by the Visual Studio team) available in the Visual Studio Gallery under Tools | Extensions and Updates. Because Ionic has its own command-line interface (CLI), which makes it pretty easy to use, and it builds on top of Cordova, let’s explore that route and create a new app from the Ionic command line:

#install ionic and cordova (once) and create the app(s)
npm install-g ionic cordova
#create a new app using the tabs template
#Could be one of several templates – run the command:
#ionic templates
#or specify url of a custom template
ionic start myIonic tabs
cd myIonic
#add platform support to the project
ionic platform add android
ionic platform add ios

I don’t have the Android SDK installed so I can’t run it yet. But it’s simple to install it via the TACO-CLI, so I’ll do that and then launch the emulator. Keep in mind that TACO provides much more than just a CLI, as noted earlier. I also want to point out that the Ionic, TACO and Cordova command lines can all work together:

#install JAVA, Android SDK, Gradle, and so forth if they don’t yet exist
taco install-reqs android
#run in the browser
ionic serve
#run in an emulator
ionic emulate android

What About iOS?

For iOS, you can debug and run your Apache Cordova application using either Visual Studio Code or Visual Studio. You’ll need access to a Mac at some point if you want to run the app on a device or in the Apple simulator, so if you’re already on Mac OS, you can run and debug directly from within Visual Studio Code as shown previously to launch on a device or in the simulator.

If you prefer to develop on Windows, you have several options. If you’re using Visual Studio, you can use the Apache Ripple simulator on Windows (bit.ly/1QOqXxK) as it’s configured out of the box to simulate iOS (see Figure 5). If you want to run and debug in the Apple  simulator (on OS X) or on an iOS device,  you’ll need a Mac with Xcode installed and the remote agent installed on the Mac so Visual Studio or Visual Studio Code can communicate with it, as outlined at bit.ly/1XC36H3. You can get a three-month Parallels subscription to run Windows on your Mac from Visual Studio Dev Essentials. Last, you can use one of the various Mac cloud services such as MacInCloud and run in the cloud as shown at bit.ly/1QOrYpz.

iOS Run/Debug Options in Visual Studio
Figure 5 iOS Run/Debug Options in Visual Studio

As mentioned, you’ll need Xcode installed on your Mac, but if you use the taco install-reqs, that command will also install ios-sim, which allows you to launch the iOS simulator from the command line, and use ios-deploy to install and debug apps onto iOS devices outside of Xcode.

Loading in Visual Studio Code

At this point, I have an application folder that was created by Ionic. This is a Cordova application. I can load either Visual Studio Code or Visual Studio and point it to this folder and start working with it. I could now go to the Debug tab in Visual Studio Code, choose a target, and start debugging, as shown in Figure 1.

Now let’s take a look at the structure of the application. Index.html is the main page and loads Ionic and Angular. You’ll quickly note that Ionic uses custom tags, such as <ion-nav-view>, but more on those shortly. Index.html is the root page that hosts the other pieces of content delivered through Angular. Ionic consists of two references, the css and the js, which has Angular bundled with it:

 

<!-- compiled css (from sass) output -->
<link href="css/ionic.app.css" rel="stylesheet">
<!-- ionic & angularjs bundled together js -->
<script src="lib/ionic/js/ionic.bundle.js"></script>

Figure 6 shows the index.html body tag. The flow in this application is URI-controlled. Those URIs are registered in app.js, as shown in Figure 7. If you’re an MVC developer, think of these as your routes. Figure 7 shows just a segment here that’ll be extended later. As you can see, two URLs are handled here: /tab and /tab/dash. What gets loaded when the app loads? The $urlRouter­Provider.otherwise function provides a default of /tab/dash, so when you start with index.html, you also process /tab/dash. 

Figure 6 The Index.Html Body Tag

<body ng-app="starter">
  <!--
    The nav bar that will be updated as we navigate between views.
  -->
  <ion-nav-bar class="bar-stable">
    <ion-nav-back-button>
    </ion-nav-back-button>
  </ion-nav-bar>
  <!--
    The views will be rendered in the <ion-nav-view> directive below
    Templates are in the /templates folder (but you could also
    have templates inline in this html file if you'd like).
  -->
  <ion-nav-view></ion-nav-view>
  </body>

Figure 7 App.js Containing Angular UI Router

// app.js
// ..
  $stateProvider
// Setup an abstract state for the tabs directive
.state('tab', {
  url: '/tab',
  abstract: true,
  templateUrl: 'templates/tabs.html'
})
// Each tab has its own nav history stack:\
.state('tab.dash', {
   url: '/dash',
  views: {
    'tab-dash': {
      templateUrl: 'templates/tab-dash.html',
      controller: 'DashCtrl'
    }
  }
})
// Extra code removed for brevity...
// If none of the above states (urls)
// are matched, use this as fallback
$urlRouterProvider.otherwise('/tab/dash');

The first URI is marked abstract, which means it will never directly navigate to /tabs. It will be utilized, though, whenever any of its children are accessed. Since the default is /tab/dash, this will cause both views to be processed for these URIs. A Cordova app doesn’t run in a Web browser but a WebView, so there’s no exposed URI a user would type into. This navigation is handled completely via hrefs or JavaScript. The states define what HTML to show for a URI, as well as what controller will handle the business logic.

The ./js/controllers.js file in this template contains the rudimentary business logic tied to each URI-template combination. The $scope variable is used simply to assign data that the templates can use. If I had customer information to return, I could just use:

$scope.customer = { "firstName": "John" };

Keeping this simplicity in mind, here’s the ChatDetail controller assigning data from a Chat service defined in services.js and, in the second example, the controller assigning a JSON object to a variable called settings I can bind in my template:

// controller.js
angular.module('starter.controllers', [])
// ...code omitted for brevity
.controller('ChatDetailCtrl', function($scope, $stateParams, Chats) {
  $scope.chat = Chats.get($stateParams.chatId);
})
.controller('StorageCtrl', function($scope) {
  $scope.settings = {
    enableCloudStorage: true
  };
});

The default tab interface is made of two templates—the tab interface (from /tab) and whichever of the three child views tabs-html contains, depending what the user selects. The HTML for the interface is shown in Figure 8. Notice how I use ion-tabs and ion-tab to define the tabs control and an individual tab, and then ion-nav-view to host the actual child views.

Figure 8 The HTML for the Default Tab Interface

<ion-tabs class="tabs-icon-top tabs-color-active-positive">
  <!-- Dashboard Tab -->
  <ion-tab title="Status" icon-off="ion-ios-pulse"
    icon-on="ion-ios-pulse-strong"
    href="#/tab/dash">
    <ion-nav-view name="tab-dash"></ion-nav-view>
  </ion-tab>
  <!-- Chats Tab -->
  <ion-tab title="Chats" icon-off="ion-ios-chatboxes-outline"
    icon-on="ion-ios-chatboxes"
    href="#/tab/chats">
    <ion-nav-view name="tab-chats"></ion-nav-view>
  </ion-tab>
  <!-- Storage Tab -->
  <ion-tab title="Storage" icon-off="cloud-outline" icon-on="cloud"
    href="#/tab/storage">
    <ion-nav-view name="tab-storage"></ion-nav-view>
  </ion-tab>
</ion-tabs>

This is the default tab-dash view displayed when the app loads. It’s rendered inside of the tab-dash <ion-nav-view> noted earlier:

<ion-view view-title="Dashboard">
  <ion-content class="padding">
    <h2>Welcome to Ionic</h2>
    <p>
    This is the Ionic starter...
    </p>
  </ion-content>
</ion-view>

Ionic also understands navigation between views. If you’re running this in a browser and click back, you go to the previous view. If you’re running on a Windows Phone or Android device and use the back button, you’ll also navigate to the previous view. This is configured inside of Index.html:

<ion-nav-bar class="bar-stable">
  <ion-nav-back-button>
  </ion-nav-back-button>
</ion-nav-bar>

Platform-Specific CSS

There are still some platform-specific things to do, such as determining which icons to show, but for this I need to know on which platform I’m running. There are several ways to detect this. I could use JavaScript from the default cordova-device plug-in using device.platform, but that means being stuck with a bunch of if/else logic to display icons. Remember, these apps are made of Web pages. I don’t want to sound simplistic, because “just Web pages” can run full 3D apps at near native speeds (like WebGL), but in this case, because I’m using just HTML/CSS/JS, I can style the app with just CSS. Ionic provides a library called Ionicons, available at Ionicons.com. And definitely checkout the cheat sheet at Ionicons.com/cheatsheet.html.

When you build for a platform by running cordova build <platform>, one change that happens is the <body> element gets an additional CSS class for that platform. For example, if you run on an Android, the body tag will be some variant of:

<body class="platform-android">

You can use this to your advantage to display platform-specific icons by using a mix of the Sass stylesheet language and Ionic Icons. The following command configures my Ionic application with Sass:

#tell Ionic to configure/create Sass and make the .scss file
ionic setup sass

If you’re using Visual Studio, this step works, but be sure to first configure Task Runner Explorer to run the Gulp task to compile the Sass to CSS. Not sure what Gulp or the Task Runner Explorer is? Check out my article on Grunt and Gulp at msdn.com/magazine/mt595751.

If you want Visual Studio to automatically compile your LESS, Sass, CoffeeScript, JSX, ES6 and Stylus files in your Web projects, you can use the Web Compiler extension to process them. Find it at bit.ly/1O0LP3x. This isn’t required here because I’m going to use Gulp, but either method works.

Now let’s open up the ./scss/ionic.app.scss file and add in some Sass. There’s loads of information out there about Sass, but the one thing to note here is that it lets you do things with CSS that would otherwise require quite a bit more text to accomplish. If I want a platform-specific cloud icon to show up and a default icon when there isn’t a platform-specific one, I can edit the .scss file to add the code shown in Figure 9.

Figure 9 Using Sass to Define Platform-Specific Cloud Icons

// Handle any platform not defined
.cloud{
  @extend .ion-ios-cloud
}
.cloud-outline{
    @extend .ion-ios-cloud-outline
}
// Android-specific
.platform-android {
  .cloud{
    @extend .ion-android-cloud
  }
  .cloud-outline{
    @extend .ion-android-cloud-outline
  }
}
// iOS-specific
.platform-ios .platform-win32{
  .cloud {
    @extend .ion-ios-cloud
  }
  .cloud-outline{
    @extend .ion-ios-cloud-outline
  }
}

Then I can manually trigger the Gulp task for this and run it:

#compile sass
gulp sass
Using gulpfile ~\myIonic\gulpfile.js
[11:28:24] Starting 'sass'...
[11:28:26] Finished 'sass' after 2.1 s
#now run it!
ionic emulate android

One thing you can immediately see is the platform-specific tab placement, shown in Figure 10. There were no code changes required for this—it’s how Ionic operates. Another change, albeit very slight, is the difference in the cloud icon. Because of the simple CSS modification, I’m rendering platform-specific icons for each platform.

Ionic on Android and iOS
Figure 10 Ionic on Android and iOS

Providing Application Updates

Applications are services. In other words, if you want to ensure a healthy community around your applications, you don’t deploy once and then forget about them. You should continually update to deliver fixes and features. A very compelling reason to choose Cordova apps over fully native apps is that you can deliver updates out of the store. App approvals on iOS can, for example, take weeks. What do you do if you have a new bug fix or feature that needs to go out immediately?

CodePush is a service from Microsoft that allows out-of-store updates for applications as long as there are no native code changes in the Cordova apps (which would typically be from adding new plug-ins). You can change assets such as the HTML/CSS/JavaScript and create a new release. Releases of your applications are stored in Microsoft Azure and completely managed by CodePush so you don’t have to worry about any infrastructure. Let’s look at the steps required to bring a version of my Android Ionic app up to the cloud:

npm install -g code-push-cli
code-push register
code-push app add myIonic

These commands give you a CodePush deployment key, which you can add to the end of the /config.xml file using a <platform> element:

<widget id="com.ionicframework.myionic594688" version="0.0.1"
  xmlns="https://www.w3.org/ns/widgets" xmlns:cdv="https://cordova.apache.org/ns/1.0">
  <!-- other config removed for brevity -->
  <platform name="android">
    <preference name="CodePushDeploymentKey"
      value="YourKeyGeneratedInPriorStep" />
  </platform>
  <platform name="ios">
    <preference name="CodePushDeploymentKey"
      value="YourKeyGeneratedInPriorStep" />
  </platform> 
</widget>

Now you just need to tell CodePush to check for updates and sync the changes. If changes are found, they’re downloaded and updated at the next launch of the app. There are asynchronous and synchronous ways of checking, but I’ll keep it short and sweet here with sync. In app.js , there’s a nice place to check for updates to my application when the Cordova APIs are ready. Typically, in Cordova apps you know you’re ready to go by hooking into deviceready:

window.addEventListener('deviceready', {})

Because I’m using Ionic, it will listen for the deviceready event and, in turn, call $ionicPlatform.ready, which is already provided in the template in app.js. I just need to add one line of code there to sync with CodePush:

// app.js
angular.module('starter', ['ionic', 'starter.controllers', 'starter.services'])
.run(function($ionicPlatform) {
  $ionicPlatform.ready(function() {
    window.codePush.sync();
    // ...
  });
})

I made a small HTML update to the /templates/tab-dash file. To release this change, I simply ask Cordova to prepare it, and then CodePush it. First I need the version of the app that’s noted in the /config.xml:

<widget id= "com.ionicframework.myiconic594688" version= "0.0.1"

Next, I’ll do the whopping two steps to prepare and push this change to the CodePush service:

#get my app ready (makes changes only in the /platforms folders)
#we could also cordova prepare or taco prepare – all do the same thing
ionic prepare
#push this release up to the cloud
#we use /assets as those are the prepared html/js/css files
code-push release myIonic ./platforms/android/assets/www 0.0.1 --deploymentName Production

Now, when the application is launched from Visual Studio Code, I can see the app checking for updates in the Visual Studio Code Debug Console:

Debug Console
Launching for android (This may take a while)...
App successfully launched
Attaching to android
Forwarding debug port
Attaching to app.
[CodePush] An update is available.
[CodePush] Downloading update package ...
[CodePush] Package download
[CodePush] Installing update package ...
[CodePush] First update: back up package information skipped.
[CodePush] Install succeeded.

That’s it! In this configuration, it takes two launches of the app to realize the changes. The first downloads the updates when the new build on CodePush is detected upon startup; the second time the app launches with the applied updates. Note that this entire workflow can be used in production or testing. Everything I’ve discussed works fine in the emu­lator, as well as live, and I can see the results in my Android emulator.

Wrapping Up

The workflow I’ve discussed involves developing, preparing and pushing your apps to the CodePush service. If you’re a continuous deployment aficionado, there’s a complete workflow outlined at bit.ly/1QpydG4. This workflow additionally outlines deployments from Visual Studio Team System and using HockeyApp to manage beta distributions (crash reporting and user feedback, too!). If you’re hoping to hit the ground running, Subhag Oak has a helpful self-contained workshop you can find at bit.ly/1QpCwBt.

Both Angular and Ionic have new major versions in the works (2.x for each of them) that are both available now for checking out. It was a bit early to talk about them here because as of this writing they haven’t been released.

This has been a quick tour of one of the most exciting areas of mobile app dev right now. Please check out some of the following resources for great documentation and to keep on top of upcoming changes:

Until next time!


Adam Tuliper is a senior technical evangelist with Microsoft living in sunny SoCal. He’s a Web dev/game dev Pluralsight.com author, and all-around tech lover. Find him on Twitter: @AdamTuliper or at channel9.msdn.com/Blogs/AdamsGarage.

Thanks to the following Microsoft technical experts for reviewing this article: Subhag Oak, Ricardo Minguez Pablos and Ryan Salva


Discuss this article in the MSDN Magazine forum