TrackMaven's next Monthly Challenge meetup will cover mobile development. To help kickstart some projects, in this post I'll cover the basics of one way to get started creating a cross-platform mobile app.
We'll be using Ionic, a framework for making beautiful, responsive mobile apps using HTML5 and AngularJS. Ionic sits on top of Cordova and has a complicated relationship with PhoneGap, both of which can also be used individually for writing cross-platform mobile applications. A few other possible options for getting started in mobile development:
- React Native is (as of this post) not yet publicly released but shows tremendous promise
- Go native with the Android SDK and start jumping through hoops to develop on Apple's iOS
- Try out Kivy for writing mobile (and non-mobile) apps in Python! I personally haven't had great luck getting Kivy apps to compile on multiple devices, but it's definitely an appealing concept
- Appcelerator's Titanium uses a JavaScript-based SDK and covers iOS, Android, Windows, BlackBerry and HTML5; I haven't tried it out, but have heard that in some cases (especially on older devices) it may be more performant than Ionic, being a step or two closer to native code
For this Ionic example, we're going to cover the following: - Push a sample app to iOS and Android emulators + devices (without forking over $99 to Apple) - Set up custom styles using Sass - Convert the sample JS to CoffeeScript with automatic gulp compilation - Add custom icons and a splash screen
Let's get to it!
Dependencies
First off, you'll need to have Node.js installed, then install the ionic NPM package:
sudo npm install -g cordova ionic
You can now create a new Ionic project using one of three templates:
blank- you guessed ittabs- includes a title header bar and "home", "star", and "settings" buttons on a footersidemenu- includes a collapsible lefthand sidebar
ionic start myprojectname sidemenu cd myprojectname/ ionic serve
After changing into the myprojectname directory (or whatever you want to name it), running ionic serve will start up the sample project on localhost and automatically open the running app in your browser. Ship it!
One important note: when testing your app locally, because of CORS rules, you won't be able to access any external data. For instance, calling an API or even loading in a picture from the web will be blocked by default on most browsers.
There are a few ways around this; for instance, you could use JSONP instead of $http calls, mess with request headers, load up a separate brower without CORS protection... or my preferred method: just use a Chrome plugin to toggle CORS on and off. This isn't the most secure, idea, so make sure you're only purposefully allowing cross domain requests while testing your app, not browsing the web.
Run it on iOS
Before diving into the code, let's make sure we can get the app on all our devices. For loading the project onto an iOS device, the first steps is to have XCode. Next you'll need to add iOS as a platform for the Ionic project and build the project for iOS:
ionic platform add ios
ionic build ios
This will create a .xcodeproj file in /platform/ios. Optionally, you can install and run the iOS simulator to run the app within an on-screen iDevice of choice:
npm install -g ios-sim
ionic emulate ios
To get the project running on a physical iDevice, previously you would have had to sign up for a $99 developer license, then build/load the project onto the device from within XCode. However, Ionic has recently released a wonderful workaround in their Ionic View for iOS.
With the Ionic View app, you can first sign up for an account with Ionic here, then download the free app from the iTunes store onto your device of choice. Finally, just run:
ionic upload
You'll need to authenticate your Ionic account, then the project will be uploaded here and available for viewing/testing from within Ionic View on the iOS device!
Run it on Android
First, let's get dependencies set up. We'll need:
- ant; run
ant -versionto check if you have it already - the Android SDK; if you're using brew, run
brew install android-sdk - all default selected packages from Android's package manager. Run
androidto enter the manager; you may need to run this command multiple times to re-enter the package manager and get all the defaults installed.
Now we can add android as a platform to the Ionic project:
ionic platform add android
If you get the error:
Error: ANDROID_HOME is not set and "android" command not in your PATH.
You'll need to add the path to android to your PATH. To find the right location, run android and note the root folder of the SDK, then find the full path to the specific tool; for me, having installed through brew, the path to add was /usr/local/Cellar/android-sdk/r21.1/tools
Now we can build the project for Android:
ionic build android
Optionally, if you want to use an Android emulator to test on various devices, we can set up an "AVD" - an Android Virtual Device. Full details are available here, but the short version is:
- Run
android list targetsto get a list of possible images along with IDs - Choose an image and run
android create avd -n {name} -t {ID}, where {name} is for instance "myandroid" and {ID} is the target ID number. - Some platforms have multiple ABIs (like choosing an API, but at the machine code level), in which case you'll need to specify one using the
-bflag
As an example, I used:
android create avd -n andefault1 -t 2 -b default/armeabi-v7a
Finally, spin up the emulator:
ionic emulate android
To get the app running on a physical Android device, plug the device into USB and run:
ionic run android
If nothing happens, you might need to swipe the device's top menu down and select an option like "charge only" mode. You can use the Android debugger to list all available devices and check if the phone is being recognized:
adb devices -l
Roll your own Sass
We've still got a default template! We can customize the styling of the app using Sass. To get set up, first run this intuitive command:
ionic setup sass
All of our Sass will be loaded in the file ./scss/ionic.app.scss - this will point to www/lib/ionic/scss for many separate SCSS files that can all be customized in style. Take a look at the color definitions at the top of www/lib/ionic/scss/_variables.scss:
$light: #fff !default;
$stable: #f8f8f8 !default;
$positive: #4a87ee !default;
$calm: #43cee6 !default;
$balanced: #66cc33 !default;
$energized: #f0b840 !default;
$assertive: #ef4e3a !default;
$royal: #8a6de9 !default;
$dark: #444 !default;
For a quick and highly visible win, we can change around the hex codes in this file to modify the app's main color palette.
Running ionic serve will use gulp to check for and automatically recompile any Sass changes.
Set up CoffeeScript
At TrackMaven, we write a lot of CoffeeScript instead of raw JavaScript; this tends to save a lot of time/eyesore/etc if you know what you're doing, especially coming from a Python background. So, let's set up this Ionic project to compile coffee files into JS!
First, some minor updates to the main page, index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
<title></title>
<link href="css/ionic.app.css" rel="stylesheet">
<script src="lib/ionic/js/ionic.bundle.js"></script>
<script src="cordova.js"></script>
<script src="js/app.js"></script>
</head>
<body ng-app="app">
<ion-nav-view></ion-nav-view>
</body>
</html>
Here we just renamed ng-app from "starter" and point to a (future) compiled app.js file rather than individual JS files.
Now, move the current app.js and controllers.js files to a new folder /www/coffee and rename then to have .coffee extensions. We could leave them as-is, since JavaScript is valid CoffeeScript, but let's convert them to actual CoffeeScript so that we can more easily make modifications later.
app.coffee ends up looking something like:
angular.module('app', ['ionic', 'app.controllers'])
.run(($ionicPlatform) ->
$ionicPlatform.ready ->
# Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
# for form inputs)
if (window.cordova && window.cordova.plugins.Keyboard)
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true)
if (window.StatusBar)
# org.apache.cordova.statusbar required
StatusBar.styleDefault()
)
.config(($stateProvider, $urlRouterProvider) ->
$stateProvider.state('app', {
url: "/app",
abstract: true,
templateUrl: "templates/menu.html",
controller: 'AppCtrl'
}
).state('app.search', {
url: "/search",
views: {
'menuContent': {
templateUrl: "templates/search.html"
}
}
}
).state('app.browse', {
url: "/browse",
views: {
'menuContent': {
templateUrl: "templates/browse.html"
}
}
}
).state('app.playlists', {
url: "/playlists",
views: {
'menuContent': {
templateUrl: "templates/playlists.html",
controller: 'PlaylistsCtrl'
}
}
}
).state('app.single', {
url: "/playlists/:playlistId",
views: {
'menuContent': {
templateUrl: "templates/playlist.html",
controller: 'PlaylistCtrl'
}
}
})
# if none of the above states are matched, use this as the fallback
$urlRouterProvider.otherwise('/app/playlists')
)
While our controllers.coffee file is as follows:
angular.module('app.controllers', [])
.controller('AppCtrl', ($scope, $ionicModal, $timeout) ->
# Form data for the login modal
$scope.loginData = {}
# Create the login modal that we will use later
$ionicModal.fromTemplateUrl('templates/login.html', {
scope: $scope
}).then((modal) ->
$scope.modal = modal
)
# Triggered in the login modal to close it
$scope.closeLogin = ->
$scope.modal.hide()
# Open the login modal
$scope.login = ->
$scope.modal.show()
# Perform the login action when the user submits the login form
$scope.doLogin = ->
console.log('Doing login', $scope.loginData)
$scope.closeLogin()
)
.controller('PlaylistsCtrl', ($scope) ->
$scope.playlists = [
{ title: 'Reggae', id: 1 },
{ title: 'Chill', id: 2 },
{ title: 'Dubstep', id: 3 }
]
)
.controller('PlaylistCtrl', ($scope, $stateParams) ->
)
Now we need to set up CoffeeScript auto-compilation to get our .coffee files loaded into app.js whenever a change is made. In /gulpfile.js, add the following:
At the top with the other global variable declarations:
var coffee = require('gulp-coffee');
Add a folder to the compilation paths:
var paths = {
sass: ['./scss/**/*.scss'],
coffee: ['./www/**/*.coffee']
};
Add coffee to the watch task:
gulp.task('watch', function() {
gulp.watch(paths.sass, ['sass']);
gulp.watch(paths.coffee, ['coffee']);
});
Then add the full coffee task:
gulp.task('coffee', function(done) {
gulp.src(paths.coffee)
.pipe(coffee({bare: true}).on('error', gutil.log))
.pipe(concat('app.js'))
.pipe(gulp.dest('./www/js'))
.on('end', done)
})
Finally, you'll need to add gulp-coffee:
npm install gulp-coffee --save
Then make sure that gulp-coffee has been added to package.json as a dependency. Now run ionic serve and make changes to the .coffee files to automatically recompile app.js and reload the app!
Add custom icons and splash screen
This step used to be a hassle to manage, but Ionic has recently automated nearly the entire process:
- Add a root
resourcesdirectory to the project - In
resources, add a 192x192px or largericon.pngfile (or.psd,.ai) - In
resources, add a 2208x2208px or largersplash.pngfile (or.psd,.ai) - Run
ionic resourcesto automatically generate all the appropriate files - Add the
--iconor--splashoptions to update only one set of resource
This post from Ionic has more details around the particular design aspects, but that's about it!
Some possible next steps...
- Set up an actual login page with authentication with JSON Web Tokens
- Use Angular $http requests to load in some external data
- Add in infinite scroll to query endless paginated data (what else are smartphones for?)
- Publish your app
- ????
- Profit!
