This article first appeared on Full Stack Training.
So you had enough of writing web applications and now you'd like to up your game and try to write mobile apps? But who has the time to learn new programming languages and technologies - or even two if you want to develop for both Android and iOS. Luckily there's a framework that allows you to use your existing skills (AngularJS) to develop mobile applications. In this article we will investigate how to setup and get started with the Ionic Framework and we'll also put together a simple application.
The full application code is available on GitHub - https://github.com/fullstacktraining/ionic-starwars-app
To get up and running all you need to do is install Ionic via npm by executing npm install -g ionic cordova
Cordova (formerly known as PhoneGap) is an Apache project (well technically it's an Adobe project, but you can read the history of the company somewhere else should you be interested) that gives you a framework to develop your mobile applications. Ionic is a framework that builds on top of Cordova.
For the purposes of this blog post I am going to assume that you have the appropriate environment setup which includes XCode and Android Studio. (I am working on a Mac which makes the iOS development a lot easier).
To get all the prerequisites out of the way let's also install Genymotion. We are going to use an Android system as a virtual machine via Genymotion to test and emulate the Android app as the standard emulator that comes with Android Studio takes a long time to start even when you have the optimisation settings in place as per the Ionic documentation. (I once left the emulator for 30 minutes and it still didn't start my app.)
Using the Genymotion interface add a new virtual device. This can be anything - I am using the Google Nexus 5 - 5.1.0 - API 22 virtual machine.
To setup a virtual device please follow this guideline: https://www.genymotion.com/#!/developers/user-guide
With these steps we are ready to go and start writing some code.
Let's start with a blank project. Ionic comes with a pretty decent generator that can generate whole application templates for you but we are going to stick to the 'blank' template.
The following command ionic start starwars blank
will create a folder called 'starwars' and generate a blank project for us.
The files that we are going to (mostly) be working with are under the www
folder.
In order to run this project we can now simply execute ionic serve
in the project's folder. This should open your default browser automatically and load 'localhost:8100' where you can see your Ionic application.
If you receive an error message stating that port 8100 is taken you can run
ionic serve -p XXXX
with a port number that is not taken by another application.
Let's also run our application in an Android and in an iOS emulator as well.
In order to do this we need to add these platforms to the project - we'll use the ionic command again to achieve this. Run ionic platform add ios
to add the iOS platform and run ionic platform add android
to add the Android platform.
At this stage it would probably make sense to add the Android SDK. Let me reiterate, as I'm using a Mac OS and I have XCode installed I don't need to do anything special to be able to create iOS apps. Android is a different story. Once you have the Android Studio installed select 'Configure' from the application's main screen and then click on 'SDK Manager'. In that window Find the 'Launch Standalone SDK Manager link to manage the SDKs:
(You can also start the Standalone SDK manager by executing the following command: /Users/{your-username}/Library/Android/sdk/tools/android
Once you have the window open install the appropriate Android SDK version. Ionic is going to throw very meaningful error message so if you're not sure what you need to install look for clues in your cli.
The last thing you need to do is to add the $ANDROID_HOME
(SDK location) to your $PATH
. On my system this looks like this: export ANDROID_HOME=/Users/{your-username}/Documents/android/sdk
. On yours the path may be different.
With these configuration settings out of the way, let's fire up our (blank) mobile app.
As mentioned earlier we are using not going to use the built-in Android emulator, instead we are going to use Genymotion. In order for our app to be sent to the Genymotion virtual machine we have to do two things:
ionic run android
as opposed to ionic emulate android
.Go ahead and start the Genymotion virtual device and once it's running (i.e. you see the stock Android apps) execute the run command stated above.
Running the iOS version is slightly easier (if you're on a Mac). Execute ionic emulate ios
which should launch the iOS Simulator.
At this time you should have the blank template running in your browser as well as in iOS and Android.
Note that we did not build our projects - we don't need to do that as the run/emulate commands build the project automatically. If you want to build a project without emulating it you can simply execute
ionic build ios
It's time to add some code. As mentioned at the beginning of this post we are going to use AngularJS to build up an application. Ionic also comes with a set of CSS classes as well as AngularJS directives.
If you navigate to your project folder you'll find a www
folder. That's the folder which contains the source code for our application and that is the folder that we'll be using to build out our mobile app.
If you have worked with Angular apps before you won't find anything strange in there - there's a js
folder that has an app.js
with some cordova/ionic specific code in it. We can easily go ahead and add controllers, filters and other Angular goodies to our application just as if it was an app for the web.
Our application is going to have two menus - one to list all the Star Wars films in a list item, and the second menu will list Star Wars characters. Tapping on the films or the characters will bring up a subsequent view of the given film/character and it'll display more information. The data is going to come from an openly available Star Wars API.
In this article we'll work on putting together the film menu and as a homework you can put together the functionality for the second (characters) menu.
In order to get started let's start working on the interface; Let's start by placing a menu into our application.
We'll retrieve some Star Wars character and film information from a web service.
Let's go ahead and change the content of the <body>
to be the following:
<ion-nav-bar class="bar-stable">
<ion-nav-back-button></ion-nav-back-button>
</ion-nav-bar>
<ion-nav-view></ion-nav-view>
Both <ion-nav-bar>
and <ion-nav-view>
are AngularJS directives provided to us by Ionic.
This gives us a back button which will allow us to go back in the current navigation stack and a navigation view.
Ionic is also able to keep track of the navigation history and it uses AngularUI's router instead of the build in AngularJS $route service. For more information visit the AngularUI Router documentation. In a nutshell the AngularJS $route service requires you to organise your application around URLs (routes) whereas the AngularUI Router allows you to organise your application around states, which in turn can have routes and other behaviour attached to them.
In light of the above let's add the routing configuration to our application by editing app.js
:
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('tab', {
url: '/tab',
abstract: true,
templateUrl: 'templates/tabs.html'
})
.state('tab.films', {
url: '/films',
views: {
'tab-films': {
templateUrl: 'templates/films.html'
}
}
});
// set the default route
$urlRouterProvider.otherwise('/tab/films');
});
With the above configuration we are setting up a 'tab' state with a 'films' route, and when we hit that endpoint a template file should be loaded which is defined by the templateUrl
property.
The template tabs.html should contain the following code:
<ion-tabs class="tabs-icon-top tabs-color-active-positive">
<ion-tab
title="Films"
icon-off="ion-ios-videocam-outline"
icon-on="ion-ios-videocam"
href="#/tab/films"
>
<ion-nav-view name="tab-films"></ion-nav-view>
</ion-tab>
<ion-tab
title="Characters"
icon-off="ion-ios-people-outline"
icon-on="ion-ios-people"
href="#/tab/characters"
>
<ion-nav-view name="tab-characters"></ion-nav-view>
</ion-tab>
</ion-tabs>
This is adding two tabs to our application with icons.
Interestingly enough the tabs will be added to the bottom on an iOS device however they'll be displayed on the top on all Android devices. We don't need to do any alteration in our code - this is all done automatically by Ionic.
Let's also see what the films.html
is going to contain. Here we are creating a view for the films page in films.html. In this view we'll put a simple header tag so that we can check that the navigation is working.
<ion-view view-title="Films">
<ion-content>
<h1>hello</h1>
</ion-content>
</ion-view>
Of course we are going to change that hello message later on to something more constructive but for the time being consider it as a placeholder.
Time to retrieve some data! We are going to be using 'SWAPI' (aka the Star Wars API). As we are pretty much working with an Angular application we need to add a controller that will use the $http
service to access the REST endpoint henceforth there are a few changes that we need to do. First is to add the controller to our app.js
:
.state('tab.films', {
url: '/films',
views: {
'tab-films': {
templateUrl: 'templates/films.html',
controller: 'FilmsController as vm'
}
}
});
Next, we need to define this controller. For now let's create a new file films-controller.js
and write the following in it:
angular.module('starter').controller('FilmsController', FilmsController);
FilmsController.$inject = ['$http'];
function FilmsController($http) {
var vm = this;
$http
.get('http://swapi.co/api/films')
.then(function (response) {
vm.films = response.data.results;
})
.catch(function (error) {
console.log(error);
});
}
The above code is using the native $http
service in AngularJS to call the API. When the data is returned it will be saved to the viewmodel in the vm.films binding.
And finally we need two more steps: Include this newly created controller in index.html
via a <script>
tag and, create films.html
template:
<ion-view view-title="Films">
<ion-content>
<ion-item ng-repeat="film in vm.films | orderBy: 'release_date'"
>">
<div>
<h2></h2>
<p><small>Director: </small></p>
<p><small>Released: </small></p>
<p></p>
</div>
</ion-item>
</ion-content>
</ion-view>
When you test your application, it will work in your browser using ionic serve
and it will also work in Android however it will not work under iOS - the iOS version will only show a blank screen but it won't show the data, yet, in your browser you won't see any errors.
It's time to discuss how you can debug your iOS app right? If you haven't enabled the Safari Developer Tools go ahead and do that now (Safari > Preferences > Advanced tab > "Show Develop menu in menu bar"), otherwise, open your Safari browser and go to Develop > Simulator > index.html. Find the console and voilà, you'll be presented with an error message that states "Failed to load resource: The resource could not be loaded because the App Transport Security policy requires the use of a secure connection." (If you don't see anything in your console you can hit CMD+R to refresh your app from within the console.)
The above in a nutshell means that you should not be communicating to non-https REST web services from your application - and you should really not do that. But for now, we'll disable this as we are running a simple test. Let's go back to our Ionic application and find the file starwars-info.plist
and let's add the following just after the last </array>
tag:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true />
</dict>
Re-run your application in the iOS emulator and you should see data flowing in.
Ideally we should change our API call to go to an
https
endpoint as opposed to apply the above configuration change, however in some cases that may not be possible.
We can equally debug Android applications using your Chrome browser. All we need to do is to open a new tab in chrome and enter chrome://inspect
. If you have Genymotion and your app running you should see it appearing in the list of Devices. Click on the inspect link and which will trigger open Developer Tools.
Let's add one more thing - what if we want to see more information on a single film on a different tab? We'll now go ahead and add this functionality. There's one simple change that we need to do in our films template, that is, to give the <ion-item>
directive a href
attribute. Remember how we have setup our states? we have a tab state with different routes, therefore we need to specify our href
attribute to be #/tab/films
:
<ion-item
ng-repeat="film in vm.films | orderBy: 'release_date'"
href="#/tab/films/"
></ion-item>
Why
$index + 1
? I couldn't find an easier way to link to each image and theepisode_id
property doesn't give me what I want. This is unfortunately a limitation at the API end, so please consider this as a simple workaround.
As well as updating our app.js
file to have the new state:
.state('tab.film', {I
url: '/films/:filmId',
views: {
'tab-film': {
templateUrl: 'templates/film.html',
controller: 'FilmController as vm'
}
}
});
We also need to add a new controller which should look like this:
angular.module('starter').controller('FilmController', FilmController);
FilmController.$inject = ['$http', '$stateParams'];
function FilmController($http, $stateParams) {
var vm = this;
var filmId = $stateParams.filmId;
$http
.get('http://swapi.co/api/films/' + filmId)
.then(function (response) {
vm.film = response.data;
})
.catch(function (error) {
console.log(error);
});
}
And finally here's how our template should look like:
<ion-view view-title="">
<ion-content> </ion-content>
</ion-view>
And that's it - now you have a fully functioning mobile app.
As an exercise you can implement the 'Character' section of the application - try to build it out on your own. Once that's done try to also improve the code by separating out the $http
requests from the controller into your own custom AngularJS service.