Oct 24, 2019

How to do Over The Air (OTA) distribution? (This tutorial is written for Adhoc Version only)

Why OTA Distribution?

You might have wondered is it possible to distribute your iOS app easily through your website.
If you're just distributing iOS app within your organization, you get to skip Apple's iOS review process which it got its fame by its strictness. With OTA you can just start publishing iOS app without the worries being rejected by App Review.

2 Types of iOS OTA Distribution

Each one require different type of Apple Developer enrolment:
  1. Adhoc Distribution - Apple Developer license
  2. Enterprise Program - Require high cost program (Apple Developer Enterprise Program)
Caveat: For 2nd point above, OTA distribution is meant for the purpose of distributing iOS app within one organization. Distributing the iOS app publicly is an act against EULA of Apple.

Differences between adhoc and enterprise OTA distribution:
  1. Adhoc OTA distribution only allow devices registered under the Provision Profile used by iOS build
  2. Enterprise distribution require your device to manually permit and "to trust" the distribution publisher.

In this article, I would only explain how to do the OTA under adhoc build, as I haven't got the opportunity to be enrolled into Apple Enterprise Program.
Even so, if you had Googling around, you would find out the distribution steps has similarity, the only different is the available choices during ipa generating.

What do you need for Adhoc OTA distribution?

Requirements:
  • Apple Developer Program (USD 99 annually)
  • iOS Distribution Certificate & releasing Profile (to be created in Apple Developer platform)

Steps to start generating iOS app for OTA distribution


Step 1: Change your device target to "Generic iOS Device" and start "Archive"

Archive

Step 2: Once the app is successfully archived

Successful Archive

Step 3: Select distribution method (choose Ad Hoc for small group distribution)


Note: Ad Hoc distribution allows devices registered under the attached to the releasing Profile to install the iOS build (ipa) only.
Ad Hoc - Distribution Method choices

Step 4:  Enable OTA installation

This step would enable add OTA installation by adding extra script (manifest.plist) later in the exported outcome. The plist file is required to allow Safari browser identify the app and communicate with your iOS device later.
Enable OTA installation

Enter hosting URL


Step 5: Signing App With Certificate and Profile

Automatic signing - xcode will try to find a suitable signing setting, it'll create a proper certificates/profiles into the Developer account
Manual - you use back known & existing setting (next screenshot)
Re-sign app



Step 6: Compile and Export

Compilation

Final Step: Exporting

Exported Files

Step 7: Final, minimal content required for manifest.plist for hosting

From step 6 above, manifest.plist & TSAwesomeProj.ipa are the only required files for simple OTA installation. You can find a lot of information in the original manifest.plist file,
Part of the content from manifest.plist file


but what's required is just as below. This is the content enough to get OTA installation working.
Simplified Version


Finally, only a simple HTML code is needed for OTA installation to happen in Safari browser. You just need to add "href" attribute value below for hyperlink.
itms-services://?action=download-manifest&url=URL_TO_MANIFEST_FILE
Example: 
<a href="itms-services://?action=download-manifest&url=https://test.com/manifest.plist">TSAwesomeProj Sample</a>

Aug 21, 2019

Angular: What does "Cannot read property 'childNodes' of null" trying to tell?

Recently, I ran into the following error, it has no obvious problem at first until part of my component template didn't show a thing until a click event is triggered the at the screen.

Cannot read property 'childNodes' of null

The error do not directly indicate which component or code in a template has gone wrong, but you know it's an error about certain part of the code is referring to "childNodes" on a null object.

Finding out the root cause

To find out where in the template has gone wrong, I didn't suspect it's problem with HTML template. I had started with checking my component class code, and though some binding values weren't pass correctly into the template code (the template code look fine, not bad syntax at all!).

Until I gave up trying consciously, I blindly test by removing of the HTML code part by part. This is where it shed me the ray of hope, eventually, I found out it's ion-card Ionic's component doesn't accept [innerHtml] correctly.

If you insist on using ion-card, you need a proper "child node" place inside it (so now I learnt, "child node" means the child element of a parent node "ion-card".), for ion-card, one of the options is ion-card-content.
I changed my code from:

<ion-card [innerHtml]="content" padding></ion-card>

to

<ion-card>
  <ion-card-content [innerHtml]="content"></ion-card-content>
</ion-card>

Separating [innerHtml] from ion-card would solve the childNode problem above. You might have different parent node causing the null childNodes issue too. So to prevent further time wasting effort finding the root-cause and if you run into similar issue, just keep in mind, if you get similar error, it means there is problem in your template/html code.

Aug 14, 2019

TypeError: Found non-callable @@iterator - Migration of Angular 7 to 8 (upgrading Ionic: 4.6.0 > 4.7.1)

Troubleshoot migration problems

TypeError: Found non-callable @@iterator

TypeError: Found non-callable @@iterator
The non-descriptive error message Found non-callable @@iterator, for new Angular 8 user like me, has made the upgrading journey a difficult experience.

And my finding, the cause of the problem above is due to the way interceptor written in Angular 7.

Causes: HttpRequest's headers "set"

I noticed the use of request.headers.set() causing the issue above. My sample code (causes):

intercept(req: HttpRequest, next: HttpHandler): Observable> {
  let headers = req.headers;
  const sampleToken = 'SAMPLE_TOKEN';
  headers = req.headers.set('Authorization', sampleToken);

  return next.handle(req.clone({ headers }));
}


After multiple trial and error, the only way to resolve is avoid using the same way to add header with such solution.

Solution: Headers modification

- on one single header modification, do the following

const yourToken = 'YOUR_TOKEN';
const apiReq = req.clone({
  headers: new HttpHeaders({'Authorization', yourToken})
});
return next.handle(apiReq);


- on multiple headers modification, do the following

const headers = { Authorization: authToken };
const apiReq = req.clone({
  setHeaders: headers
});
return next.handle(apiReq);
P/S: header value must be in string value

Aug 13, 2019

Troubleshooting Cakephp 3 showing "Blank page" (Clue: internationalisation (i18n))

Understanding i18n

To implement multiple languages in your webpage, you need internationalisation (or i18n, start with an alphabet "i", 18 alphabet in the middle and end with an "n").

If you have came across this feature, you'll know you need your static content written in the following format:

echo __('Text to be translated');


And the "Text to be translated" will appear in your Locale folder's in "po" formatted file.

Potential cause of the "Blank page"

Overview of the findings, the "blank" page appears in:

  • complete blank screen and show nothing, 
  • browser is showing correct code in the HTML page source (without HTML code from chosen layout)
  • page goes blank without any warning/error messages

Things to check for the issue

  • is the "__()" written correctly? Make sure it's 2 underscores + text in the bracket.
  • Search your source code and find if there is any mistakenly written single underscore "_()" i18n syntax
  • be warned, you won't get any error message from single underscore i18n syntax "_()" mistake


References:
CakePHP 3 - Internationalization & Localization
CakePHP 3 - Global function __()

Apr 11, 2019

Troubleshooting Android Flashing Custom ROM / Recovery

Troubleshooting

Cannot boot into TWRP without rooting

The sentence above is somewhat misleading, "without rooting" doesn't mean "no flash", I said so because I was inexperienced, so I keep trying to boot without flashing. And I've stuck in the retrying loop until I gave up several for 2 weeks of countless attempts.
I would rather name the title to "Flashing TWRP without rooting" before anyone else get stuck in the hopeless loop again.

In order to boot into custom recovery, you cannot skip flashing the custom recovery first. Lesson learned,
No One Can Boot Into Twrp Without Flashing With TWRP Recovery first!
As a Asus device user, I did trial and error this many time until I finally learnt and come to a conclusion "we can't boot into custom recovery without flashing them beforehand".

Let me summarise the general steps after I've gone through wide amount of online articles:
  1. adb reboot bootloader (boot into fastboot mode)
  2. fastboot devices (to make sure your device is connected)
  3. fastboot flash recovery twrp.img
  4. fastboot reboot (or press and hold Volume button to boot into TWRP recovery mode)
What I've learned from this:
- Flashing TWRP will still unlock the bootloader in your device
- for Asus, flashing TWRP custom recovery will also root your device (void warranty)

I haven't seen any of my trial and error that won't root my device after flashing TWRP recovery.

You can check your device info by running command below:
  fastboot oem device-info
The Root status will show as "Rooted"

Endless re-booting bootloader/bootup screen

I encounter this problem after flashed an incorrect recovery into my device.
Potential cause: Usually, you'll run into this problem after running the command below in fashboot mode (with a unsuitable recovery file):

  fastboot flash recovery example-recovery-filename.img

Solution

To resolve this, you'll have to find the correct version of recovery rom that fits your device. If you spent hours looking for one and still no luck getting one, then you may need to consider a more generally owned by the developer community or learn to compile a custom recovery by yourself. TWRP itself is a open source project, if you need something not existence

For TWRP custom recovery, please visit: https://twrp.me/Devices/
For Asus, I usually have to keep googling at their dedicated Asus Forum - Zentalk for specific/older device's discussion.

For Asus device, if you run into unrecoverable flashing issue (bricked), you can always get back the stock (official) version of ROM/firmware from Asus official support website and flash your device back into the original device firmware.

* note: avoid losing all your data, always backup your device before flashing anything

Booting into Fastboot/Recovery Mode

1) How do I get into fastboot mode?
Run the command below in order to boot into fastboot mode from "normal UI" screen (the UI show up for normal daily use made for general user.):

  adb reboot fastboot

You can boot into fastboot mode by restart/power on your phone by holding Volume Up button (for Asus Android phone).
Steps:
  1. From power off mode, press and hold volume buttons
    1. volume up - for fastboot mode
    2. volume down - for recovery mode
  2. Keep on holding the volume button after device is powered on until the expected screen mode is correctly shown.
*note: Booting into fastboot/recovery mode doesn't render your warranty void.

2) "adb: command not found", what is missing?
Which command unavailable in your system, you can't proceed any further.
To install required tools, you need run the command above using:

  • (Window) adb.exe (ROM flashing kits)
  • (Unix) For Unix system, 2 choices: 
    • Install full official android development tools Android Studio (follow their installation tutorials), or 
    • Visit Android Studio (same url as above), scroll until Command Line Tools only section and install the only the command line tools, which will install required general tools like adb (Android Debug Bridge) and fastboot tools to access to your android devices through USB cable.

Some useful articles that worth your time (for understanding about flashing custom recovery):
Discover hidden fastboot commands
Guide to customise Android device
Install TWRP recovery on Android (no root)