Nov 15, 2015

Manually refresh (and clear cache on) current page on IonicView as needed

Ionic v1.2.4

If you've looked around for a solution to manually clear cache and refresh current page, you'll more likely found solution like below.

$ionicHistory.clearCache().then(function () {
  $state.go($state.current, {} , {reload: true});
});


After I've tried the solution above, it doesn't really work as expected, and I don't see how it clear the view cache for current page.
My First try with

$ionicHistory.clearCache().then(function () {
  $state.go($state.current, {} , {reload: true});
});

the page/view would just be redirected with $state.go(), and does not has it cache cleared (it still shows the old content).

As I roughly went through the ionic.bundle.js, I found $ionicHistory.clearCache() actually does accept array value, stateIds, as parameter to clear the view cache specifically.

So, the final solution to the expected result (manually clear cache and refresh current page):

$ionicHistory.clearCache([$state.current.name]).then(function () {
  $state.go($state.current, {}, {reload: true});
});

As you can see, I added the [$state.current.name] to specifically assign each required view cache to be cleared. And so, that's what required for $ionicHistory.clearCache() to work properly.

Nov 13, 2015

Cordova Plugin not found

Today I ran into Cordova plugin mapping issue.

Spent around 2 hours time to find out the root cause of the missing plugin,
it happens every time we rebuild iOS before any plugin is cloned.

To replicate the problem:

  1. In the cordova app project directory, remove "plugins" & "platforms" folders
  2. run `cordova platform add ios` to add platform directly (this step will fetch required plugins stated in config.xml and generate the iOS platform)
  3. Once iOS platform is ready, run `cordova run ios`.
The three steps above will lead you to problems like screenshot below.
plugins not found
Plugins not found
ERROR: Plugin 'File' not found, or is not a CDVPlugin. Check your plugin mapping in config.xml.
["File334867545","File","requestAllPaths",[]]
ERROR: Plugin 'NetworkStatus' not found, or is not a CDVPlugin. Check your plugin mapping in config.xml.
inJSON = ["NetworkStatus334867546","NetworkStatus","getConnectionInfo",[]]
ERROR: Plugin 'Device' not found, or is not a CDVPlugin. Check your plugin mapping in config.xml.
788:2282747] -[CDVCommandQueue executePending] [Line 159] FAILED pluginJSON = ["Device334867547","Device","getDeviceInfo",[]]
In case I proceed by ignoring those "plugin not found" issues,
and run the buggy app on my device.
I'll end up getting errors like below in my debugging tool's console:
deviceready has not fired after 5 seconds
Channel not fired: onPluginReady
Channel not fired: onCordovaReady


To workaround this issue,
you'll need to rebuild the cordova iOS platform again with all the plugins fetched at first.

Now, since I have all the required plugins ready (from previous added iOS platform),
I'll keep the fetched plugins (inside plugins folder) and follow the steps below.

Remove and add back the iOS platform:
  1. cordova platform rm ios
  2. cordova platform add ios

Cordova: phonegap-facebook-plugin alternative with Facebook SDK 4

There are choices of cordova Facebook login plugins available, but I found two useful at the time I'm developing my Cordova mobile app.

Those are:
- phonegap-facebook-plugin
- cordova-plugin-facebook4

phonegap-facebook-plugin is mostly recommended by the internet community at the time I am writing this, as example ngCordova - Facebook,
but it caused some UX and development issues to me as a developer.

phonegap-facebook-plugin
It uses FacebookSDK version 3.21.1, with this SDK, for iOS 9, it leads to problems like:
  • must not have bitcode enabled (activated by default in xcode 7)
  • requires manually add whitelist Facebook access every time you have your iOS platform created
  • Every login will leave a blank safari browser opened for Facebook browser login (unless user has native Facebook app installed)
  • Symlink of FacebookSDK is broken with direct cordova plugin add phonegap-facebook-plugin
cordova-plugin-facebook4
The purpose this plugin is to make use of FacebookSDK 4.
  • It helps saving the drudgery on adding LSApplicationQueriesSchemes and NSSApplicationTransportSecurity items.
  • support bitcode encoded, so you don't need to deactivate it every rebuild of the cordova platform.
  • Time saving on git cloning to workaround broken FacebookSDK symlink.

Added 2015-11-13 10:55 PM
the only bad thing about cordova-plugin-facebook4
there is different between plugin cloned by cordova added through new platform build (cordova platform add ios/android) with the one manually added with cordova cli (cordova plugin add).

Differences:

# Cloned with platform add Plugin added with cordova cli
1 Need manual add of
LSApplicationQueriesSchemes
Auto added
2 Bitcode_enabled to be disabled support bitcode_enabled
3 Facebook login with
safari browser
Do not need external browser
for Facebook login 

Even though both cordova cloned with platform and cli are the same cordova-plugin-facebook4,
but at the time I developing,
it does has differences as your installation method is different.

Furthermore,
if you already have cordova-plugin-facebook4 saved into config.xml,
running cordova plugin add cordova-plugin-facebook4 (with variables) again will update itself into a different version plugin.

my case:
    <plugin name="cordova-plugin-facebook4" spec="~1.3.0-0">
        <variable name="APP_ID" value="FB_APP_ID" />
        <variable name="APP_NAME" value="FB_APP_NAME" />
    </plugin>

run cordova plugin add cordova-plugin-facebook4 --save (with variables) again, will become:
    <plugin name="cordova-plugin-facebook4" spec="~1.1.0">
        <variable name="APP_ID" value="FB_APP_ID" />
        <variable name="APP_NAME" value="FB_APP_NAME" />
    </plugin>

Nov 10, 2015

Use ngCordova inAppBrowser executeScript interacting with remote WebView content

Even the idea/solution very straight forward, but still, I learned ngCordova's inAppBrowser the hard way.

I am using cordovaInAppBrowser to handle my remote content in mobile's WebView.
Tutorials accessible out there mostly give you the idea of how it works in direct content display, like:


// insert Javascript via code / file
$cordovaInAppBrowser.executeScript({
  code: 'alert("some text in the webview");'
});


Yes, it does tell the code is working in the webview,
but my problem is how can the value be manipulated and pass back to our cordova app?
So what we need is more than pop up alert box in webview.

As I learned,

  1. executeScript code WOULD JUST work when we have target as "_blank"

    // executeScript must has "_blank" as target
    $cordovaInAppBrowser.open(YOUR_URL, '_blank');
    

  2. the executeScript code would just run once as inAppBrowser page load/refresh depend on the events triggered:
    • $cordovaInAppBrowser:exit
    • $cordovaInAppBrowser:loaderror
    • $cordovaInAppBrowser:loadstop
    • $cordovaInAppBrowser:loadstart

So what you can do with this executeScript feature is to 'grab' your expected variable.

$rootScope.$on('$cordovaInAppBrowser:loadstop', function(e, event){
  $cordovaInAppBrowser.executeScript({
    code: "var executable = function(data) { return data; }; executable(yourJsVariable || null);"
  }).then(function(data) {
    // Do something here with your obtained value
    if (angular.isObject(data)) {
      console.log(data);
    }
  });
});

In my case above, my detection of yourJsVariable starts as the page load of the inAppBrowser stops.

Reference: Issue injecting external script with executeScript method in Cordova
(The reference above is the best working example code I found so far.)

Nov 7, 2015

Short Note: HTML5 localStorage - think "resume"

Just realise something that I have been using it so long,
while still not knowing one of the best part of its "resumable" feature.

It keeps what you last stored in the localStorage, and won't lost the stored data until you do it manually.

In this case,
we can check any progress updated before any kind of interruption,
and resume the progress from there.

One of my concern was that I didn't know I can really trust the stored data while having an interruption happens in the middle of the process.

Thanks to Dive Into HTML5: HTML5 Storage in Action,
I should start utilise it with progress status data and think localStorage from know on.
(Even though it isn't a very new thing anymore)

Oct 8, 2015

Cordova Development - iOS iTunesArtwork in ad hoc distribution

As how everyone explained around the internet, you need files named "iTunesArtwork" (512px X 512px) & "iTunesArtwork@2x" (1024px X 1024px).

iTunesArtwork - 512 x 512
iTunesArtwork@2x - 1024 x 1024

And they must be filename without extension.
If your image file is iTunesArtwork.png then name it to iTunesArtwork (without the ".png").



Once you have your artwork ready, rebuild your iOS Cordova build.
cordova run ios 
After then, the generated app (applicaiton) file:
generated-app.app (in /mobile_app_root/platforms/ios/build/device/generated-app.app directory)



right click on the generated-app.app and select "Show Package Contents"




After you click [Show Package Contents], it will direct you into a new directory.
This is the directory where you should place your iTunesArtwork & iTunesArtwork@2x files.



In root folder, is where you place your  iTunesArtwork & iTunesArtwork@2x files.


Try it in iTunes app, you'll see the result.


References:
Customizing the App
iTunesArtwork And Xcode

Cordova Development - How to release an iOS build from app bundle generated from "Cordova run ios"

By running, cordova command, cordova run ios will generated an iOS application bundle in
cordova-project-directory/platforms/ios/build/device/app-name.app

 Using manual way to distribute testing build, you'll need to:


  1. Create a folder named "Payload" (only 'Payload' name would work)
  2. Paste your app-name.app into the Payload folder created in 1st step
  3. zip/compress the Payload folder
  4. once compressed, the Payload folder become Payload.zip, rename the 'Payload.zip' to app-name.ipa
  5. Place it into iTunes, and start install with it.

Sep 24, 2015

Replace default look of SELECT box or dropdownlist on mobile webview

I am using onsen-ui HTML5 UI framework to create my webview mobile app.
With the help of:
  <ons-list-item modifier="tappable">
  </ons-list-item>
On onsen-ui, it does not help inherit existing design into the select box.
An example below.

Default design of the select box on iOS
In this case,
we'll have to write a custom CSS class for the select box to replace the default design.


  .custom-select-box {
    color: inherit;
    font: inherit;
    line-height: initial;
    direction: rtl;
    overflow: hidden;
    height: 40px;
    width: 100%;
    padding: 5px 8px;
    border: none;
    box-shadow: none;
    background-color: transparent;
    background-image: none;
    -webkit-appearance: none;
       -moz-appearance: none;
            appearance: none;
    option {
      direction: initial;
    }
  }

the main part which replace the default view is:
directional: inherit;
appearance: none;
border: none;

Final result

Sep 20, 2015

Building Cordova iOS app with xcode7 run into "Build failed with Ld build/Debug-iphoneos/ normal armv7" problem

This problem occur because of the enable_bitcode feature of xcode7, and I suspect this occur is because some cordova plugin we're using does not contain bitcode.



In order to solve this problem:
As default value is a "Yes" for iOS, you'll need to disable it on your own.

To do: Disable "Enable Bitcode" setting inside Build Settings.

  1. select targeted app
  2. select "Build Settings" tab
  3. at the search bar under the tabs, search for the keyword "bitcode" (or find it under Build Options)
  4. change Enable Bitcode to "No".


Enable Bitcode



Try to Build your app again.

References:
What does enable bitcode do in xcode 7

Aug 27, 2015

URL Scheme for Android in Cordova

My project's spec:
- Onsen UI
- Cordova

Simply following the step in Custom URL scheme PhoneGap Plugin would get your android or ios app ready with custom URL scheme.

But testing it the wrong way cause my hours to figure what is wrong with my configuration.

So, in the end, I found out testing URL scheme in android is different with how we test in ios.

In ios device,  ipod as example,
we just simply use browser with "myapp://" would do all job.

Which it is different in android,
so you'll have to utilise easyhyperlinks.com to initiate the scheme correctly,
because in android, it automatically prefix url with "http://" in browsers.

You can refer to this Using Custom URL Schemes In Your Ionic Framework App and see how he tested the scheme in the easy way.

Reference:
github repo: Custom URL scheme PhoneGap Plugin
video tutorial: Using Custom URL Schemes In Your Ionic Framework App
testing tool for android: easyhyperlinks.com

Aug 25, 2015

Cordova, store image from data URL or base64

In order to get image resized and store the image right away, we need some workaround to achieve this result.

First, resize the image into thumbnail-like size
2nd, store the image into targeted directory

This is achievable with Phonegap-image-resizer


$window.imageResizer.resizeImage(function(resizedImage) {

  // Store image into local
  $window.imageResizer.storeImage(function(response) { // cache
    var filePath = response.filename,
        pathOnly = filePath.substring(0, sourceFile.lastIndexOf('/')) + '/',
        filename = filePath.split('/').pop().replace(/\#(.*?)$/, '').replace(/\?(.*?)$/, '');


    // store file into targeted location here with 
    // cordova official File plugin
    $cordovaFile.moveFile(pathOnly, filename, cordova.file.dataDirectory).then(function(success) {
      console.log(success);
    }, function(error) {
      console.log(errir);
    });


  }, function(error) {
    return error;

  }, resizedImage.imageData, {
    imageDataType: ImageResizer.IMAGE_DATA_TYPE_BASE64,
    format: 'jpg',
    filename: // new name,
    directory: // location to store,
    quality: 80
  });

}, function(error) {
  return error;

}, photo.url, 0.2, 0.2, {
  imageDataType: ImageResizer.IMAGE_DATA_TYPE_URL,
  resizeType: ImageResizer.RESIZE_TYPE_FACTOR,
  format: 'jpg'
});


Two parts here:


TL;DR

1st step, $window.imageResizer.resizeImage will resize the image
2nd step, $window.imageResizer.storeImage store image

Cursory go through resizeImage.js to better understand how to utilise the code.

Reference: Converting Large Images to Base64Strings in Ionic Framework

Aug 8, 2015

Convert base64 to Blob

I'm developing android app with AngularJs & ngCordova,
and in my case, I just can choose to get image file from it's URI.

We don't have "[Select file...]" button to click to set our file directly into $scope object.
For example:


$scope.insertFile = function (file) {

    $scope.fileObject = file;

};

I then use Cordova's plugin, $cordovaFile - readAsDataURL, to get the base64 formatted file.
It's so I can upload my picture through $http and data parameter in form of FormData().

Simply convert into Blob as below does not work:

$cordovaFile.readAsDataURL(file.directory, file.name).then(function(response) {
 // take jpeg image as example
 var imageFile = new Blob(response, {type: 'image/jpeg'}); 
 formData.append('photo', imageFile, file.name);
};

It (the conversion) breaks my image file, because `new Blob()` doesn't automatically encode base64.

I have been struggling to get a non-broken image uploaded for some time until I found a solution that solved my problem below.

Solution
I found a useful HTML5 Filesystem API library code has method that helps correctly converting base64 into Blob.

With this I corrected my code into:

$cordovaFile.readAsDataURL(file.directory, file.name).then(function(response) {
 // take jpeg image as example
 var imageFile = dataURLToBlob(response);
 formData.append('photo', imageFile, file.name);
};

It does convert my image file correctly, and my app is no longer uploading broken image again.

Reference:
http://stackoverflow.com/questions/12168909/blob-from-dataurl

Aug 5, 2015

Default Hash key of Android apk

I found the solution from this,
every IDE will have the default keystore password.

Like the case when I build my apk (android app package),
it will generated with the default hash key.

Generating an APK in the Debug Mode

  • Keystore name: debug.keystore
  • Keystore password: android
  • Key alias: androiddebugkey
  • Key password: android
  • CN (common name): CN=Android Debug,O=Android,C=US
In order to get the hash key with the keytool command, 
you need the "Key alias" and "Key password" values.


keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore | openssl sha1 -binary | openssl base64
(command for mac)

Aug 4, 2015

Enable PHP extension

Since I use homebrew to install PHP (Command: brew install php56)

And so, we'll have my php.ini file located in other directory.
which is,
/usr/local/etc/php/5.6/php.ini

so you'll have to edit this file in order to activate extension.
In the file, in the Dynamic Extensions part, you will see a list of commented out extensions.


;extension=php_bz2.dll
;extension=php_curl.dll
;extension=php_fileinfo.dll
;extension=php_gd2.dll
;extension=php_gettext.dll
;extension=php_gmp.dll
;extension=php_intl.dll
;extension=php_imap.dll
;extension=php_interbase.dll
;extension=php_ldap.dll
;extension=php_mbstring.dll


Install desired extension eg.

brew install php56-mcrypt

remove the semicolon ";" at the desired extension to enable it.
and then restart apache server with


sudo apachectl restart 

your extension would be ready for action.

OnsenUI: Change tab without pressing ons-tabber's ons-tab (with AngularJs)

I have been searching around for changing tab with AngularJs $scope function, without pressing on the ons-tabber's tab.
This function will do the trick setActiveTab()
First, you have to specify name for the tabbar,
(eg. ons-tabbar[var="myTabbar"])

use setActiveTab() with index (just like array, start from 0) for myTabbar:
myTabbar.setActiveTab(1) for my case below.

AngularJs

var app = angular.module('app', ['onsen']);

app.controller('myCtrl', ['$scope', function($scope) {
  $scope.goHome = function() {
    // setActiveTab(index, options)
    myTabbar.setActiveTab(1, {animation: 'fade'});
  };
}]);

Html

<ons-page ng-controller="myCtrl">
  <ons-toolbar>
    <div class="right">
      Toolbar - myCtrl
    </div>
  </ons-toolbar>
  
  <ons-button ng-click="goHome()">Go Home</ons-button>

  <ons-tabbar var="myTabbar">
    <ons-tab active="true" icon="ion-images" label="App" no-reload="" page="app.html" persistent=""></ons-tab>
    <ons-tab icon="fa-user" label="Home" no-reload="" page="home.html" persistent=""></ons-tab>
  </ons-tabbar>
</ons-page>

Summary
The main part:

  • use variable name for ons-tabbar
  • use setActiveTab(index) with the tabbar variable name to redirect

Aug 2, 2015

The meaning of OAuth redirect URIs

Because of my limited understanding about the technical term used in Facebook Developer,
I have been struggling hard to implement even the login provided by Facebook. 

SO I'm writing this for future reference:
Valid OAuth redirect URIs = the URL (or link) of where your Facebook login placed.

Meaning of OAuth redirect URIs
Meaning of OAuth redirect URIs

Eg. 
if your Facebook placed under the URL http://localhost/facebook/login/
then you can insert either http://localhost/ or http://localhost/facebook/login/ (exact URL) in the Valid OAuth redirect URIs input box. 

Jun 3, 2015

AngularJs UI-Router conditional Otherwise link

For easier handling of redirection of $state,
when our angularJs app handling an unknown $state name,
it'll refer to $urlRouterProvider.otherwise("/"); which have to be written inside "config()".
assuming your angular app is declared as below:

var app = angular.module([]);

app.config(['$urlRouterProvider', function($urlRouterProvider) {
  $urlRouterProvider.otherwise(function($injector, $location) {
    var AuthService= $injector.get("AuthService");
    if (AuthService.isLoggedIn()) {
      return "main";
    } else {
      return "login";
    }
  });
}]);
this allow your otherwise become dynamic depend on the login status of a user. In the code above, AuthService is an example authentication service to help determine if a user is logged in.

May 27, 2015

You just simply cannot change the date format in HTML5 date type input

By referring quite number of sites,
and did quite some number of stubborn trials...

the conclusion is that you
CANNOT CHANGE DATE FORMAT FOR HTML5 DATE TYPE INPUT

thanks a lot to the solid references:
Is there any way to change input type=“date” format?
Example provided in fiddle 

further reading, for quick & easy learning:
The date Type