Setup

Android SDK

Download Android SDK and export path in your .bashrc like export ANDROID_HOME=/home/orlovic/Android/Sdk/.

Make android, adb and emulator accessible with:

sudo ln -s /home/orlovic/Android/Sdk/platform-tools/adb /usr/bin/
echo 'export PATH="$PATH:$HOME/orlovic/Android/Sdk/tools"' >> ~/.bashrc
# or
sudo ln -s /home/orlovic/Android/Sdk/tools/android /usr/bin/

android list avd
emulator @n4 &

Configure USB access with this command:

wget -S -O - http://source.android.com/source/51-android.rules | sed "s/<username>/$USER/" | sudo tee >/dev/null /etc/udev/rules.d/51-android.rules; sudo udevadm control --reload-rules

On new device go to settings to enable USB Debugging. Settings is hidden by default, to enable go Settings -> About Phone -> Build number then tap 5 times. Then go to Settings -> Developer options and enable USB debugging. Also usefull is to enable Stay awake (screen will never sleep while charging).

lsusb # find your model
sudo vi /etc/udev/rules.d/51-android.rules
# add your model with idVendor and idProduct
sudo udevadm control --reload-rules
# plug again your device
adb kill-server
adb devices

Download Ionic

Download ionic and cordova

npm install -g cordova ionic
ionic start myIonic blank
cd myIonic
cat >> .gitignore << HERE_DOC
# from command ionic upload
.io-config.json
# bower packages
./www/lib/
# cordova js is managed by ionic
./hooks/
HERE_DOC
git init . && git add . && git commit -m "ionic start"

Run ionic

ionic platform add android # this will install cordova-plugins-(console,device, splashscreeen,statusbar,whitelist)
cordova plugin add ionic-plugin-keyboard # this is default plugin
# prefer ionic CLI to cordova CLI because it adds to package.json
ionic plugin add cordova-plugin-camera
ionic state save # others pull the code, sync with `ionic state restore`
ionic upload
git add . && git commit -m "ionic add android && ionic upload"
ionic share [email protected]

ionic serve # to see it in browser (rebuild also)
# to run in emulator with live reload and logs (rebuild also)
# --target to select emulator
ionic emulate -lcs --target=googleapi21
ionic run --target 123123 # to run on device, find device name with adb devices
# note that `run` will not rebuild, it will just install latest configured apk
# note that it will rebuild and use env vars if `-l` parameter is used
ionic serve --nobrowser # it will not open new browser, so you can use existing

livereload use ionic.config.yml to look watchPatterns (default is www/js/* and !www/css/**/*). To run gulp task you an use gulpStartupTasks: ['watch'] in ionic.config.yml. Ionic cli 2 does not run gulp task on ionic serve so you need to add in gulpfile.js (gulpSartupTasks will not help).

// gulpfile.js
gulp.task('serve:before', ['default']);
gulp.task('emulate:before', ['default']);
gulp.task('run:before', ['default']);

// default should run all tasks and watch task
gulp.task('default', ['sass', 'coffee', 'jade', 'watch']);

Sass

Note that sass is already in gulpfile, but it is not started. Follow using sass instructions to link to compiled sass file.

ionic setup sass
cat >> .gitignore << HERE_DOC
# when using /scss css is autogenerated
./www/css/
HERE_DOC
git rm www/css -r
git add . && git commit -m "ionic setup sass"

Coffeescript

cat >> .gitignore << HERE_DOC
# coffee script files are all appended to one file
www/js/_coffeescript_build.js
HERE_DOC

sed -i ionic.project -e '/gulpSartupTasks/a "coffee",'

sed -i gulpfile.js -e '/var sass/a \
var coffee = require("gulp-coffee");'

sed -i gulpfile.js -e '/var paths/a \
  coffee: ["./www/**/*.coffee"],'

sed -i gulpfile.js -e '/gulp.task..watch/a \
  gulp.watch(paths.coffee, ["coffee"]);'

sed -i gulpfile.js -e '/gulp.task..default/c \
  gulp.task("default", ["sass", "coffee", "watch"]);'

cat >> gulpfile.js << 'HERE_DOC'

gulp.task('coffee', function(done) {
  gulp.src(paths.coffee)
  .pipe(coffee({bare: true}).on('error', gutil.log))
  .pipe(concat('_coffeescript_build.js'))
  .pipe(gulp.dest('./www/js'))
  .on('end', done);
});
HERE_DOC

# add to package.json
npm install gulp-coffee gulp-concat --save
sed -i www/index.html -e '/<\/head>/i \
    <!-- coffe script is bundled to this file -->\
    <script src="js/_coffeescript_build.js"></script>'

git add . && git commit -m "Enable coffee script"
touch www/js/_coffeescript_build.js # just to not raise file not found error

Note that it does not work nice on first run ionic serve , you need to update some coffee file to get pipe to reload. Note that we added coffee to ionic.project gulpStartupTasks, but sometimes browser cache will prevent some changes to show up, so try to reload the page.

When I add dependency for default task, than javascript changes are not recognized, sometimes… wip

// gulpfile.js
gulp.task('default', ['sass', 'coffee', 'jade']);

Jade

Adding jade with: npm install gulp-jade --save-dev. Than

cat >> .gitignore << HERE_DOC
# here we will build html from jade
www/jade_build
HERE_DOC
sed -i ionic.project -e '/gulpSartupTasks/a "jade",'
sed -i gulpfile.js -e '/var sass/a \
var jade = require("gulp-jade");'
sed -i gulpfile.js -e '/var paths/a \
  jade: ["./www/**/*.jade"],'
sed -i gulpfile.js -e '/gulp.task..watch/a \
  gulp.watch(paths.jade, ["jade"]),'
cat >> gulpfile.js << 'HERE_DOC'

gulp.task('jade', function (done) {
  return gulp.src(paths.jade)
    .pipe(jade())
    .pipe(gulp.dest('www/jade_build/'));
});
HERE_DOC
git commit -am "Enable jade"
# gulpfile.js
gulp.task('default', ['sass', 'jade']);

You can convert all html files to jade but please check them. index.html should be in html format since that path www is fixed.

Also note that template path should be prefixed with jade_build in your router file, for example templateUrl: 'jade_build/js/dashboard/dashboard.html' Check example of login controller for ionic

Adding CONSTANT for SERVER_URL

You probably need to change api url from command line, since ionic does not pass arguments to gulp command, we need to use env variables (note that they must be prefix ie before actual command). ionic has some problems passing parameters to gul

# run with prefix env
SERVER_URL=my-domain.local:3001 ionic serve
SERVER_URL=production ionic emulate
# put in your app
cat >> www/js/app.constant.coffee << HERE_DOC
PRODUCTION_SERVER_URL = 'https://www.trk.in.rs'
STAGING_SERVER_URL = 'http://trk.herokuapp.com'
LOCAL_SERVER_URL = 'http://mylocationsubdomain.my-domain.local:3001'
SERVER_ENV_URL = 'staging' # this is default
SERVER_URL = switch SERVER_ENV_URL
  when 'production' then PRODUCTION_SERVER_URL
  when 'staging' then STAGING_SERVER_URL
  when 'local' then LOCAL_SERVER_URL
  else SERVER_ENV_URL
angular.module 'starter'
  .constant 'CONSTANT',
    SERVER_URL: SERVER_URL
    ENV: SERVER_ENV_URL
HERE_DOC
# put to your gulpfile.js
npm install gulp-if gulp-replace --save-dev
sed -i gulpfile.js -e '/shelljs/a \
var replace = require("gulp-replace");\
var gulpif = require("gulp-if");\
\
var serverEnvUrl = false; // default is what is defined in constants.coffee\
if (["production", "staging", "local"].indexOf(process.env.SERVER_ENV_URL) != -1) {\
  serverEnvUrl = process.env.SERVER_ENV_URL;\
  console.log("Using serverEnvUrl=" + serverEnvUrl);\
} else if (process.env.SERVER_ENV_URL) {\
  serverEnvUrl = "http://" + process.env.SERVER_ENV_URL;\
  console.log("Using serverEnvUrl=" + process.env.SERVER_ENV_URL);\
} else {\
  console.log("Using default SERVER_ENV_URL");\
}'

sed -i gulpfile.js -e '/gulp.src(paths.coffee/a \
  .pipe(gulpif(!!serverEnvUrl, replace(/SERVER_ENV_URL =.*/g, "SERVER_ENV_URL = '" + serverEnvUrl + "'")))'

Adding other packages, for example RailsResource

When you add using bower install angularjs-rails-resource --save you need also to include it in www/index.html (it can be before or after app.js) and you need to declare dependency in www/app.js

    <!-- Rails Resource
    https://github.com/FineLinePrototyping/angularjs-rails-resource -->
    <script src="lib/angularjs-rails-resource/angularjs-rails-resource.js.min"></script>

Checkout adding angular-devise authentication

Adding tabs templates

This patch was generated with git diff > patchfile and can be applied with patch -p1 < patchfile

You can create different tabs for different users. Just $state.go 'customer-tabs.dashboard' after customer logs in. Customer tabs are the same as user tabs (abstract state, controller, template).

diff --git a/mobileApp/www/index.html b/mobileApp/www/index.html
index c0d6225..faa3c73 100644
--- a/mobileApp/www/index.html
+++ b/mobileApp/www/index.html
@@ -22,12 +22,18 @@
   </head>
   <body ng-app="starter">
 
-    <ion-pane>
-      <ion-header-bar class="bar-stable">
-        <h1 class="title">Ionic Blank Starter</h1>
-      </ion-header-bar>
-      <ion-content>
-      </ion-content>
-    </ion-pane>
+    <!--
+      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>
 </html>
diff --git a/mobileApp/www/js/_coffeescript_build.js b/mobileApp/www/js/_coffeescript_build.js
deleted file mode 100644
index e69de29..0000000
diff --git a/mobileApp/www/js/account/account.controller.coffee b/mobileApp/www/js/account/account.controller.coffee
new file mode 100644
index 0000000..ddcd815
--- /dev/null
+++ b/mobileApp/www/js/account/account.controller.coffee
@@ -0,0 +1,3 @@
+angular.module 'starter'
+  .controller 'AccountController', ->
+    vm = this
diff --git a/mobileApp/www/js/account/account.html b/mobileApp/www/js/account/account.html
new file mode 100644
index 0000000..2e72f67
--- /dev/null
+++ b/mobileApp/www/js/account/account.html
@@ -0,0 +1,9 @@
+<ion-view view-title="Account">
+  <ion-content>
+    <ion-list>
+    <ion-toggle  ng-model="settings.enableFriends">
+        Enable Friends
+    </ion-toggle>
+    </ion-list>
+  </ion-content>
+</ion-view>
diff --git a/mobileApp/www/js/app.config.coffee b/mobileApp/www/js/app.config.coffee
new file mode 100644
index 0000000..ba02b6e
--- /dev/null
+++ b/mobileApp/www/js/app.config.coffee
@@ -0,0 +1,37 @@
+angular.module 'starter'
+  .config ($stateProvider, $urlRouterProvider) ->
+    $stateProvider
+      .state 'tab',
+        url: '/tab'
+        abstract: true
+        templateUrl: 'js/tabs/tabs.html'
+      .state 'tab.dashboard',
+        url: '/dashboard'
+        views:
+          'tab-dashboard':
+            templateUrl: 'js/dashboard/dashboard.html'
+            controller: 'DashboardController'
+            controllerAs: 'vm'
+    .state 'tab.chats',
+        url: '/chats'
+        views:
+          'tab-chats':
+            templateUrl: 'js/chats/chats.html'
+            controller: 'ChatsController'
+            controllerAs: 'vm'
+      .state 'tab.chat-detail',
+        url: '/chats/:chatId'
+        views:
+          'tab-chats':
+            templateUrl: 'js/chats/chatDetails.html'
+            controller: 'ChatDetailsController'
+            controllerAs: 'vm'
+      .state 'tab.account',
+        url: '/account'
+        views:
+          'tab-account':
+            templateUrl: 'js/account/account.html'
+            controller: 'AccountController'
+            controllerAs: 'vm'
+
+    $urlRouterProvider.otherwise '/tab/dashboard'
diff --git a/mobileApp/www/js/chats/chatDetails.controller.coffee b/mobileApp/www/js/chats/chatDetails.controller.coffee
new file mode 100644
index 0000000..f7768da
--- /dev/null
+++ b/mobileApp/www/js/chats/chatDetails.controller.coffee
@@ -0,0 +1,4 @@
+angular.module 'starter'
+  .controller 'ChatDetailsController', ($stateParams, Chats) ->
+    vm = this
+    vm.chat = Chats.get $stateParams.chatId
diff --git a/mobileApp/www/js/chats/chatDetails.html b/mobileApp/www/js/chats/chatDetails.html
new file mode 100644
index 0000000..9ea8222
--- /dev/null
+++ b/mobileApp/www/js/chats/chatDetails.html
@@ -0,0 +1,8 @@
+<ion-view view-title="">
+  <ion-content class="padding">
+    <img ng-src="" style="width: 64px; height: 64px">
+    <p>
+      
+    </p>
+  </ion-content>
+</ion-view>
diff --git a/mobileApp/www/js/chats/chats.controller.coffee b/mobileApp/www/js/chats/chats.controller.coffee
new file mode 100644
index 0000000..029eedc
--- /dev/null
+++ b/mobileApp/www/js/chats/chats.controller.coffee
@@ -0,0 +1,15 @@
+angular.module 'starter'
+  .controller 'ChatsController', (Chats) ->
+    # With the new view caching in Ionic, Controllers are only called
+    # when they are recreated or on app start, instead of every page change.
+    # To listen for when this page is active (for example, to refresh data),
+    # listen for the $ionicView.enter event:
+    #
+    #$scope.$on('$ionicView.enter', function(e) {
+    #});
+
+    vm = this
+    vm.chats = Chats.all()
+    vm.remove = (chat) ->
+      Chats.remove(chat)
+    console.log vm.chats
diff --git a/mobileApp/www/js/chats/chats.html b/mobileApp/www/js/chats/chats.html
new file mode 100644
index 0000000..89ad519
--- /dev/null
+++ b/mobileApp/www/js/chats/chats.html
@@ -0,0 +1,17 @@
+<ion-view view-title="Chats">
+  <ion-content>
+    <ion-list>
+      <ion-item class="item-remove-animate item-avatar item-icon-right"
+        ng-repeat="chat in vm.chats" type="item-text-wrap" href="#/tab/chats/">
+        <img ng-src="">
+        <h2></h2>
+        <p></p>
+        <i class="icon ion-chevron-right icon-accessory"></i>
+
+        <ion-option-button class="button-assertive" ng-click="vm.remove(chat)">
+          Delete
+        </ion-option-button>
+      </ion-item>
+    </ion-list>
+  </ion-content>
+</ion-view>
diff --git a/mobileApp/www/js/dashboard/dashboard.coffee b/mobileApp/www/js/dashboard/dashboard.coffee
new file mode 100644
index 0000000..520b24e
--- /dev/null
+++ b/mobileApp/www/js/dashboard/dashboard.coffee
@@ -0,0 +1,4 @@
+angular.module 'starter'
+  .controller 'DashboardController', ->
+    vm = this
+
diff --git a/mobileApp/www/js/dashboard/dashboard.html b/mobileApp/www/js/dashboard/dashboard.html
new file mode 100644
index 0000000..88c3906
--- /dev/null
+++ b/mobileApp/www/js/dashboard/dashboard.html
@@ -0,0 +1,5 @@
+<ion-view view-title="Dashboard">
+  <ion-content class="padding">
+    <h2>Welcome to Ionic</h2>
+  </ion-content>
+</ion-view>
diff --git a/mobileApp/www/js/resources/chats.resource.coffee b/mobileApp/www/js/resources/chats.resource.coffee
new file mode 100644
index 0000000..4aa4755
--- /dev/null
+++ b/mobileApp/www/js/resources/chats.resource.coffee
@@ -0,0 +1,33 @@
+angular.module('starter')
+  .factory 'Chats', ->
+    # Might use a resource here that returns a JSON array
+    # Some fake testing data
+    chats = [
+      {
+        id: 0
+        name: 'Ben Sparrow'
+        lastText: 'You on your way?'
+        face: 'img/ben.png'
+      }
+      {
+        id: 1
+        name: 'Max Lynx'
+        lastText: 'Hey, it\'s me'
+        face: 'img/max.png'
+      }
+    ]
+    {
+      all: ->
+        chats
+      remove: (chat) ->
+        chats.splice chats.indexOf(chat), 1
+        return
+      get: (chatId) ->
+        i = 0
+        while i < chats.length
+          if chats[i].id == parseInt(chatId)
+            return chats[i]
+          i++
+        null
+
+    }
diff --git a/mobileApp/www/js/tabs/tabs.html b/mobileApp/www/js/tabs/tabs.html
new file mode 100644
index 0000000..8fb1726
--- /dev/null
+++ b/mobileApp/www/js/tabs/tabs.html
@@ -0,0 +1,25 @@
+<!--
+Create tabs with an icon and label, using the tabs-positive style.
+Each tab's child <ion-nav-view> directive will have its own
+navigation history that also transitions its views in and out.
+-->
+<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/dashboard">
+    <ion-nav-view name="tab-dashboard"></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>
+
+  <!-- Account Tab -->
+  <ion-tab title="Account" icon-off="ion-ios-gear-outline" icon-on="ion-ios-gear" href="#/tab/account">
+    <ion-nav-view name="tab-account"></ion-nav-view>
+  </ion-tab>
+
+
+</ion-tabs>

Some version of node have problem starting emulator, so you can start manually emulator @n4 &

Generate icons with ionic resources. If you update icons than you should run with option --ignore-cache so it does not use cached tmp files ionic resources --ignore-cache (maybe size is too big).

IonicModal

Nice feature is to put forms inside $ionicModal You should clear data when modal is canceled

# www/js/login/login.jade
          .text-center
            a(ng-click="vm.resetPasswordModal.show()") Forgot Password?

# www/js/login/login.controller.coffee
angular.module 'starter'
  .controller 'LoginController', ($ionicModal, $scope) ->
    vm.usernameOrMobileNo = ''
    $ionicModal.fromTemplateUrl(
      'jade_build/js/login/resetPassword.html'
      scope: $scope
    ).then (modal) ->
      vm.resetPasswordModal = modal

    # clear modal fields when user cancel modal
    $scope.$on 'modal.hidden', ->
      vm.usernameOrMobileNo = ''

    vm.resetPassword = (usernameOrMobileNo) ->
      CustomerAuth.resetPassword(usernameOrMobileNo).then(
        (response) ->
          vm.resetPasswordModal.hide()
          NotifyService.toast response.data.message
          vm.usernameOrMobileNo = ''
        NotifyService.errorToast
      )

# www/js/login/resetPassword.jade
ion-modal-view
  ion-header-bar.bar.bar-header.bar-positive
    h1.title Reset Password
    button.button.button-clear.button-primary(ng-click="vm.resetPasswordModal.hide()") Cancel
  ion-content
    form(ng-submit='vm.resetPassword(vm.usernameOrMobileNo)' ng-autodisable
      ng-autodisable-class="processing")
      label.item.item-input.item-stacked-label
        span.input-label Username Or Mobile Number
        input(type="text" placeholder="Enter your username or mobile number"
        ng-init="vm.usernameOrMobileNo=''"
        ng-model="vm.usernameOrMobileNo")
      .padding
        button.button.button-block.button-positive(type="submit") Submit

Common errors

For INSTALL_FAILED_OLDER_SDK you need to lower sdk version requirement. Ionic drop support below SDK 14 so in your config.xml put

  <preference name="android-minSdkVersion" value="14"/>

On adb install my_app.apk you could get Failure [INSTALL_FAILED_ALREADY_EXISTS] rm failed for -f, No such file or directory. Or Failure [INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES] or [INSTALL_FAILED_UPDATE_INCOMPATIBLE]

Solution is to remove old application. You can use my helper ionic_find_package_name

adb uninstall `ionic_find_package_name my_app.apk`

# to find package name use ionic_find_package_name my_app.apk that is similar to
~/Android/Sdk/build-tools/21.1.2/aapt dump badging my-app-debug.apk  | grep package

You can start cordova app main activity from command line with

adb shell am start -a android.intent.action.MAIN -n `ionic_find_package_name my_app.apk`/.MainActivity

Emulator tips

  • copy paste links to emulator or device adb shell input text "some text"
  • new chrome will upcase first leter for some fields first name
  • old native browser on Android 4.0.3 does not support WebSockets

VirtualBox and Android Emulator

If you want to emulate and get this error

ioctl(KVM_CREATE_VM) failed: Device or resource busy
ko:failed to initialize KVM

it’s because VirtualBox is running and using kvm. You can edit emulator tools/android --avd to use some other than x86, for example armeabi-v7a. Then they can work in the same time ionic serve --target myArmeabiDevice l. But Intel Atom (x86) is much faster. Also enable Emulation Options: Use Host GPU.

Adb devices offline

If your phone is offline, try to shut down and turn on again.

Debug with ADB

You can see android device logs with grep (key with space does not work)

adb help 2>&1 | less -R
# option needs to be before other arguments
adb -e shell
adb -d logcat | grep 'Web Console' # -d is for device only
adb logcat | grep 'chromium\|Web Console' # new androids use chromium
adb install -r platforms/android/build/outputs/apk/android-debug.apk
# adb help will show you that -r means replace

adb -s emulator-5556 install my-app.apk # if you have two emulators you can
# specify which emulator
adb -d install my-app.apk # directs to only connected usb device

telnet localhost 5554
  geo fix 19 45 # to fix gps location, first is longitude than latitude

To download apk source and grab it from phone you can use

adb shell pm list packages |grep netatmo
adb shell pm path com.netatmo.homecoach
adb pull /data/app/com.netatmo.homecoach-IvieREGOfx3gu0ROd6jygQ==/base.apk

If it is cordova app than you will be able to see the source

unzip base.apk
 inflating: assets/www/cordova.js

DNS for local server

On Android emulator 127.0.0.1 will not point to your machine! Also your /etc/hosts file will not be used. You can change hosts file on android phone. On old sdk all changes last only until you reboot emulator so in order to make it permanent, we copied system.img to system-qemu.img (from your android sdk system image folder to your avd folder in your home) about images. cp $ANDROID_HOME/system-images/android-17/default/x86/system.img ~/.android/avd/n4.avd/system-qemu.img We also needed to increase partition-size.

On new SDK you only need to start emulator with option -writable-system and adb root;adb remount before those changes. You need to use -writable-system option for all future starting of emulator because if won’t boot up.

emulator -writable-system -avd n4 &
adb root
adb remount
adb pull /system/etc/hosts .
echo "192.168.2.4 my-domain.local" >> hosts
echo "192.168.2.4 mylocationsubdomain.my-domain.local" >> hosts
adb push hosts /system/etc
adb shell 'cat /etc/hosts'

Root android device

xda root.

Releasing the app and deploy to google play store

If you don’t have a key, you should generate and always use that. If app is already installed on a phone with different keys, user can not update it (since it uses the same package name, he needs to remove the app and than install again). If you lost the keystore, than only solution is to publish as different package on google play. You need to rename existing app Store Listing -> Title so you can create new one with same name. Also you can not use the same package name so choose different if you are using different key to sign.

keytool -genkey -v -keystore ~/config/keys/my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000

You should remove all unnecessary stuff for publishing like cordova plugin rm cordova-plugin-console

Also you need to update config.xml to customize your project.

  • use your reverse domain for widget id: id=rs.in.trk.123
  • customize name, description and author
cordova build --release android
# this will generate platforms/android/build/outputs/apk/android-release-unsigned.apk
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ~/config/keys/my-release-key.keystore platforms/android/build/outputs/apk/android-release-unsigned.apk alias_name # this change the file inline
zipalign -v 4 platforms/android/build/outputs/apk/android-release-unsigned.apk my_app.apk

You can use ionic_publish my bash function.

When you want to publish another release, you need to update config.xml version attribute (do not touch id attribute).

On google play, when you create new application and upload apk, you need to fill store listing: title, short and full description, high-res icon, feature graphic, 2 non android screenshots, target country, mark as free app…

Old browsers

Since old phones without Chromium suffer from poor javascript performance, maybe crosswalk could help.

NgCordova

When you use wrapper around some plugin than you need to install ngCordova

bower install ngCordova
sed -i www/index.html -e '/<\/head>/i \
    <!-- ngCordova that is installed using bower -->\
    <script src="lib/ngCordova/dist/ng-cordova.js"></script>'

Angular $log service $log.debug(myObj); can be used and disabled easilly in app.config.js with $logProvider.debugEnabled(false) so only $log.error() or $log.info() will be shown (don’t know how to disable those two).

If you see [object Object] (probably old angular version) than you can use $log.debug(JSON.stringify(response.data));. Better way is to create window.log

# www/js/app.js
window.old_console = window.console;
window.console = (function() {
  var console = {};
  console.log = function(message) {
    if (typeof(message) == 'object')
      message = JSON.stringify(message);
    window.old_console.log(message);
  };
  return console;
})();

Notifications

I would rather user android toast instead of ngMessages. Toast-PhoneGap-Plugin is a source for cordova-plugin-x-toast. We could use wrapper $cordovaToast, but it is not neccessary.

cordova plugin add cordova-plugin-x-toast # or https://github.com/EddyVerbruggen/Toast-PhoneGap-Plugin.git
ionic state save
mkdir www/js/services
cat >> www/js/services/notify.service.coffee << 'HERE_DOC'
angular.module('starter')
  .service 'NotifyService', () ->
    prepareMessage = (message) ->
      text = message;
      if typeof message == 'object'
        text = JSON.stringify(message)
      text
    this.toast = (message) ->
      text = prepareMessage(message)
      if window.plugins && window.plugins.toast
        window.plugins.toast.show text, 'long', 'center'
      else
        alert text
      console.log text
    this.errorToast = (message) ->
      # if message is response, try to get message.data.error
      this.toast(message)
      # notify your server
    this.log = (message) ->
      text = prepareMessage(message)
      $log.debug text
    return
HERE_DOC

Content security policy

http://content-security-policy.com/

<html>
  <head>
    <meta http-equiv="Content-Security-Policy" content="
    default-src * 'self' data: gap: https://ssl.gstatic.com;
    style-src 'self' 'unsafe-inline';
    script-src * 'unsafe-inline';
    media-src *;
    ">
  </head>
</html>
  • ui-router can add resolve: { auth: function($auth) { return $auth.validateUser() } }, it will hang if user is not logged in. In ionic screens are nested states so it will be blank page. Better is to use completelly different state and resolve auth on abstract tabs
  • ui-router for subpages should be on same level as page, since we render them on whole <ion-nav-view>
  • rack-cors use all actions [:get, :post, :delete, :put, :patch, :options, :head]. I have strange error that all requests were OPTIONS instead of POST.

Ready

For cordova plugins you should wait for ready and test if it exists. $ionicPlatform.ready is also fired in browser, so you need to check if plugin exists.

angular.module 'starter'
  .controller 'DashCtrl', ($scope, $ionicPlatform) ->
    $ionicPlatform.ready ->
      window.SignalStrength && window.SignalStrength.dbm (db) ->
        $scope.signal = db

Local storage

Cookies are sent for each request, so do not use it to store data. localStorage or sessionStorage (expires when browser tab or window is closed) is much more reliable.

window.localStorage.setItem("url", "http://trk.in.rs");
window.localStorage.getItem("url");
window.localStorage.removeItem("url");

Devise authentication

For backend you should enable CORS

angular_devise gem works nice. Just follow README.

ng-token-auth#validateUser is called on page load so user does not need to log in again.

Token can be save to localStorage to work on device and emulator. I’ve not succeed to have working auth in browser for ionic.

if something is not working, wipe node_modules and run npm install

LocalStorage (or ng-token-auth) does not work with emulator -l livereload, so don’t use -l.

Do not check if user is valid with angular.equals($scope.user, {}) in controller since validateUser returns promise that will resolve after controller has been initialized. Better is to

// login.controller.js
  $auth.validateUser()
  .then(function() {
    $state.go('tab.dashboard');
  });

Or the best approach is to store all login state redirections in run

http://yeghishe.github.io/2015/08/13/my-gulp-files-ionic-app.html https://github.com/pluralsight/guides/blob/master/published/front-end-javascript/ionic-framework-a-definitive-10-000-word-guide/article.md

Tips

  • to find ionic version run in browser and type in console ionic.version (or grep bower.json file). ionic -v will give you ionic CLI version which is not related to ionic version
  • to clean files that are inside www but git ignored, you can git clean -xdf www but you need to npm install && bower install
  • ion-refresher is nice widget to refresh on pulling. For automatic refresh when you navigate to preview view

      # With the new view caching in Ionic, Controllers are only called
      # when they are recreated or on app start, instead of every page change.
      # To listen for when this page is active (for example, to refresh data),
      # listen for the $ionicView.enter event:
      #
      #$scope.$on('$ionicView.enter', function(e) {
      #});
    

    This is also called on initial navigation (when controller is instantiated). So I write like

      # www/js/customers/customers.controller.coffee
      $scope.$on '$ionicView.enter', (e) ->
        vm.doRefresh()
    

    If you want to reload whole app (probably you want to clear some cache of controllers and services), go to root or login url and reload. On some old devices it will stay on that page (account page) and reload, which is not as intended.

      console.log "You have been signed out"
      $state.go 'login'
      window.location.reload() # this is not necessary
    
  • external links can not be used but you can use plugin inappbrowser link

    cordova plugin add cordova-plugin-inappbrowser
    
    # www/js/login/login.controller.coffee
      vm.open = ->
        cordova.InAppBrowser.open(
          'http://www.google.com'
          '_blank'
          'location=yes'
        )
    

    if you want to run some javascript than you should use jQueryLite since it already exists in app or vanila javascript console.log inside page will be the same as in ionic app. If you need to post to external url which did not enable cors, you can not use $http.post but plain form submission. Since I like to keep working in browser and emulator, I moved submit button outsite of form and than ng-click will submit the form or load inappbrowser and pass the data. Closing inappbrowser could be navigating to mobile/close for which I set eventListener. Passing data to inappbrowser could be with executeScript and retrieving with executeScript with callback.

    
    # www/js/dashboard.controller.coffee
      vm.proceedOnlinePayment = (paymentFields) ->
        # since cors is not enabled we will use inappbrowser for device,
        # or submit form for emulation
        if window.cordova && window.cordova.InAppBrowser
          browser = cordova.InAppBrowser.open(
            'payu-form.html'
            '_blank'
            'location=yes'
          )
          browser.addEventListener 'loadstop', ->
            console.log 'loadstop'
            data =
              form_fields: paymentFields.form_fields
              url: paymentFields.payu_link
            browser.executeScript(
              code:
                """
                processData('#{JSON.stringify(data)}');
                console.log("calling processData from inappbrowser");
    
                """
            )
        else
          document.forms.myForm.submit()
    
    // www/js/dashboard/onlinePaymen.jade
    ion-modal-view
      ion-header-bar.bar.bar-header.bar-positive
        h1.title 
        button.button.button-clear.button-primary(ng-click="onlinePaymentModal.hide()") Cancel
      ion-content
        form(id="payment-form" action="https://test.payu.in/_payment.php"
          accept-charset="UTF-8" method="post" name='myForm')
          input(type="hidden" ng-repeat="i in vm.paymentFields.form_fields"
          name="" id="" value="")
          label.item.item-input.item-stacked-label
            span.input-label Total Amount
            strong 
    
          div(ng-if="vm.paymentFields.example_credit_card")
            span This is example card used only in development
            input(value="")
            div Name 
            div CCV 
            div Month/Year
              | /
        .padding
          button.button.button-block.button-positive(ng-click="vm.proceedOnlinePayment(vm.paymentFields)") Pay Now
    
  • you can push your code and assets without going through app store. Ionic Deploy, Code Push, PhoneGap ContentSync, PhoneGap hydration.
  • use authentication can we implemented with 3th party service: Firebase Auth, GCP Auth, Auth.io, Auth0, Stormpath, Fabric Digits, Amazon Cognito, Azure Auth, Ionic Auth

Android studio

if you receive error

org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:transformClassesAndResourcesWithProguardForDebug'.

than disable minify

# app/build.gradle
#android {
#  buildTypes {
#    debug {
-      minifyEnabled true
+     minifyEnabled false

if you receive error

java.lang.RuntimeException: com.android.builder.dexing.DexArchiveMergerException: Error while merging dex archives:

than

# app/build.gradle
# android {
#   defaultConfig {
+     multiDexEnabled true //important

if you receive error

org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:transformClassesWithMultidexlistForDebug'.

than you need to increase memory for dex https://stackoverflow.com/questions/32807587/com-android-build-transform-api-transformexception

# local.properties
+ org.gradle.jvmargs=-XX:MaxHeapSize\=512m -Xmx512m

# maybe this could help, but not neccessary
# app/build.gradle
# android {
+    dexOptions {
+        javaMaxHeapSize "4g" //specify the heap size for the dex process
+    }

To Invalidate gradle cache you should go to File -> Invalidate Caches / Restart…

Build -> Clean Project

rm -rf .gradle/ app/build ~~~