Wednesday, December 27, 2017

Delete existing database and recreate it by EntityFramework code first for asp.net core project

1. Delete the existing database
option 1: From the Visual Studio's View->SQL Server Object Explorer, delete the existing database. Note, just delete the database file from disk is not enough, as the table is still kept in system table list.
option 2: from command line, run
dotnet ef database drop

2. Delete all files from asp.net project's Migrations folder

3. From command line, run the below command to add the migration cs script to the asp.net core project
dotnet ef migrations add initial

4.Create the database and table
option 1:  From the command line, run the below command to
dotnet ef database update

option 2: call Database.Migration method from cs code when starting app. This option is particular useful for Azure deployment with connection settings as command line is not a good option for Azure cloud deployment.
public class Program
    {
        public static void Main(string[] args)
        {
            var host = CreateWebHostBuilder(args).Build();
            
            using (var scope = host.Services.CreateScope()){
                var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
                context.Database.Migrate();
            }
            host.Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();
    }
}

Wednesday, December 13, 2017

iOS: set different row height for different prototype cells in a single table view

Since IOS 8.1,  supporting different height for different row prototype is quite easy.

First in storyboard, setting height constraint for different row prototype, then adding those rows into a single UITableView will show the height constraints are ignored at runtime and the row height is always using the row height configured in TableView row height property.

In order to apply the row height constraint defined in row prototype, add the below code in the viewController's viewDidLoad method

        tableView.estimatedRowHeight = 44

        tableView.rowHeight = UITableViewAutomaticDimension

The estimatedRowHeight can be set to the same value as configured in tableview's row height property.

Note, inside the cell, the vertical constraint must set to require the vertical cell height. For example, if a button has top and bottom and height constraints added as the cell height, it will work. But if the button only has top and height constraints, then it will not work, as the cell will be shrunked to estimated row height.

Monday, December 4, 2017

Fix "could not resolve project cordovaLib" error after upgrading to android studio 3.0.1

After upgrading to Android studio 3.0.1, when building cordova project, if error happens for

Unable to resolve dependency for ':@debug/compileClasspath': Could not resolve project :CordovaLib. Open File Show Details

Then it can be fixed by updating the android module's build.gradle with the below change:

dependencies {
    compile fileTree(dir: 'libs', include: '*.jar')
    // SUB-PROJECT DEPENDENCIES START    debugCompile(project(path: "CordovaLib", configuration: "debug"))
    releaseCompile(project(path: "CordovaLib", configuration: "release"))
    compile project(':CordovaLib')
// SUB-PROJECT DEPENDENCIES END
} 

Wednesday, November 29, 2017

Mac change folder owner for permission to update file

When upgrading to High Sierra on the mac, somehow the owner needs to be updated due to an IT requirement.

After the ownership change, a lot of files are locked due to the current user does not own the file.

Updating the owner of the file and folder from Get Info page does not work as expected. So the best way to fix the issue is running chown from terminal as


sudo chown -Rv i826633 FolderPathToChangeTheOwner 

Saturday, November 18, 2017

iOS 11 UIBarButtonItem may not work on real device

One iOS project has a view controller containing a table view and a toolbar, the toolbar has a UIBarButtonItem to open another view controller.

After upgrading an old ios project with XCode 9.1 and swift 4, running it on iOS 11 real device shows clicking on the UIBarButtonItem does not work as expected. However when running on iOS 11 simulator, clicking on the button works fine.

Even if after reverting all the change made during the upgrading in the project, the issue still happens, so it indicates the issue is caused by rebuilding the project using the ios 11 SDK and running it on ios 11 device.

The more investigation indicates the issue is caused by a tap gesture recognizer added in the viewcontroller's view object. The tap gesture is used for dismissing the keyboard.

In ios build prior to iOS 11, even if adding a tap gesture recognize on the view controller's view object, the toolbar button's handle will still be called. However, in iOS 11 sdk, when clicking the button, only the tap gesture recognizer's handler will be called, the button item's click handler will not be called.

The fix is quite simple, instead of adding the gesture recognizer to the view controller's view object, just add it to the tableview' view object. So that when clicking on the bar button item, it will not trigger the tap gesture recognizer's event handler

Original code:
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(TravelDateViewController.handleTap(recognizer:)))

self.view.addGestureRecognizer(tap)

After the fix:
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(TravelDateViewController.handleTap(recognizer:)))

self.tableView.addGestureRecognizer(tap)

Monday, November 13, 2017

Missing Firebase module error when adding unit test target into existing ios project

An ios xcode swift project was created a while ago with swift 3.0. Later when adding a unit test target into the project, a build error happens due to import module gets unknown module or missing Firebase module error.

For the unknown module error, it is because the module name has been changed after the project was created, the import statement requires module name instead of target name. So fix this error, just go to application's build settings' module name field and use the module name for the import statement.

The second error is due to swift version mismatch, the application target uses swift 3.0, while the new unit test target uses swift 4.0, and the Firebase library downloaded by pod file can only have one version, so both target must have the same version. From the target's Build Settings, change the swift language version to 3.0

The pod file was generated only have the configuration for application target, after adding the unit test target, it needs to be updated to also have Firebase library for unit test target as below, and then run "pod update" to update the pod

source 'https://github.com/CocoaPods/Specs.git'

platform :ios, '9.0'

target 'myapp' do
  use_frameworks!
  pod 'Firebase/Core'
  pod 'Firebase/AdMob'
  
  target 'UnitTests' do
        use_frameworks!
        inherit! :search_paths
        pod 'Firebase'
  end

end


Finally after import the application module into the test file, be sure @testable is used, so the internal class can be used by the unit test methods.

import XCTest
@testable import myapp

Avoid main thread check warning in iOS project when firebase is used

When starting ios project which integrated with Google Firebase library, the app may generate few main thread check warning as below
Main Thread Checker: UI API called on a background thread: -[UIApplication delegate]
PID: 740, TID: 51958, Thread name: (none), Queue name: FIRAnalyticsQueue, QoS: 9
Backtrace:
In addition, the ios unit test target may not be able to run at all if the main thread warning exists.

The reason is the old Google Firebase library 4.0.0 and below has not been updated for the new ios 11 and xcode 9, the issue can be fixed by updating the Google Firebase library through the pod file by running the below command from ios project root folder in mac terminal:

pod update

Currently it will update the Google Firebase library to
Installing Firebase 4.5.0 (was 4.0.0)
Installing FirebaseAnalytics 4.0.4 (was 4.0.0)
Installing FirebaseCore 4.0.10 (was 4.0.0)
Installing FirebaseInstanceID 2.0.5 (was 2.0.0)
Installing Google-Mobile-Ads-SDK 7.25.0 (was 7.20.0)
Installing GoogleToolboxForMac 2.1.3 (was 2.1.1)

Installing nanopb (0.3.8)

Friday, November 10, 2017

git merge and rebase from same and forked repository

Git Merge and rebase from same repository
Both merge and rebase can be used to integrate changes from one branch to another, however they use different approach. Consider the below case:
A forked commit history



git merge
As the name imply, when git merge is used as below
git checkout A
git merge B
(git pull B)
It appends all the change from B as a single commit to the current history of A. So all changes happened in B are added as a single new changes in A. None of the existing commit history in branch A is affected by this.
Merging master into the feature branch





git rebase
As an alternative to merge,
git checkout A
git rebase B
(git pull --rebase B)
Rebase will insert each commit happened in B into the history of A, so the changes in B get inserted at the proper history in A Rebasing the feature branch onto master



Git merge for forked repository
If you have a forked repository, usually you will make some change and then send a pull request to update the original repository. But quite often you will need to update your forked repository to bring back the change made in original master repository. Although you can do this from git bash command, but if you prefer there is an easier way to do this from github web page.

In the forked repository, click the pull request button, in the next page, click "switching the base" button as you want to merge the change from original repository to your forked repository. Then create the pull request and merge the pull request.



Friday, September 15, 2017

How to check ui5 version at runtime, and set UI5 version in launchpad



1. How to check UI5 verison
When the UI5 application is running, enable javascript debugger. Then from debug console, type the variable of 
sap.ui.version
The output is the currently loaded UI5 version

2. How to set HCP launchpad ui5 version
As all UI5 apps run under a fiori launchpad use the same UI5 version configured for the launchpad, so we need to set the correct UI5 version to match the fiori app's UI5 version.

2.1 open SAP Cloud Platform Cockpit admin
2.2 select and goto portal service
2.3 as each launchpad can have its own UI5 version, so select the site and click the pen button to edit
2.4 in the next screen, under "System Settings" there is a "SAPUI5 Version".
2.5 click Edit button to update the UI5 version to the expected value.
2.6 click Action button and select Take Offline
2.7. click top right the round publish icon to publish the site
2.8 open the site again and check the new ui5 version is used

Pay attention when Everyone or Anonymous roles are enabled for the fiori launchpad, when Guest user is used, errors may return due to lack the required permission. The simple way to fix this is remove Everyone or Anonymous role from the site settings.

3. from sap-ui-core.js, search 
sap.ui,{version:
to get the ui5 version

Friday, September 1, 2017

SAP Web IDE note

When creating Fiori project using SAP Web IDE, after deploying the project to fiori launchpad, starting the app from fiori launchpad may not work, and get an error of "Cannot read property 'getResourceBundle' of undefined".

One reason of this error is when creating the project the UI5 version selected from Web IDE may be different (or newer) than the UI5 version available in the deployed fiori launchpad.

The UI5 version set by web IDE is shown below
 

The fiori launch pad UI5 version is at


The web IDE's UI5 version must match or be compatible with fiori launchpad's UI5 version.

By the way, it is much easy to use Layout Editor to manage xml view than code editor.


Thursday, August 31, 2017

SAP single sign on note

When handling Single-Sign-On between apps in SAP Cloud Platfrom, there are few names used in the document:

1.SAPAssertionSSO
SAPAssertionSSO uses SAP Assertion ticket, which is passed as http header between different apps, the ticket is issued by SAP Assertion service.

2. PrincipalPropagation
Although PrincipalPropagation is a general term used for app to app single sign on, but in SAP Cloud Platform, it has a specific meaning. It refers to forward the identity of user to SAP Cloud Connector, and from there, pass the identity to the backend server, usually this is done by generate a short living client certificate by SAP Connector and then pass the client cert to backend server.
Usually, this configuration is used for Cloud platform to on-premise server communication,

3. AppToAppSSO
Both apps are configured to trust the the SAML IDP provider, so the same SAML assertion can be passed and used between the two apps.
Usually, this configuration is used for two apps both living in internet or on-premise.

https://help.hana.ondemand.com/hana_cloud_platform_mobile_services/frameset.htm?db73d2da88684c8da382f23c0ecbd28f.html


Monday, July 31, 2017

iOS UIWebView, NSURLSession cache

NSURLSession can handle cache based on the NSURLSessionConfiguration.requestCachePolicy property. 

The default value of the requestCachePolicy property is NSURLRequestUseProtocolCachePolicy (0), when the default value is used, the cache validation and loading is transparent to application implementation. For example, when a request is sent to server by NSURLSession, if the cached response is available using etag as validator. Then NSUrlSession will automatically add the "If-None-Match" header in the request before sending the request to server. If server side sees the response data is not changed based on the etag value, the server will return 304 (not modified) to client without response data. When NSURLSession gets the 304 response status from server, it will load the cached response, and call the NSURLSessionDataTask's completion block with the status code and response data loaded from the cached response, so the application will only the status code is 200 instead of 304.

If you want to prevent this automatic cache management, then you can set requestCachePolicy property to NSURLRequestReloadIgnoringLocalCacheData. For example, if etag is used for validate the cache, then nsurlsession will not add the required IF-NONE-MATCH header to the client request, and the server will always return a full response.

Note, if you want to test and verify the cache works properly in your app, you have to log the device http traffic, as the delegate of NSURLSession will never see the 304 status code returned from server, it will be automatically replaced by the status code in the cached response. Besides, do not explicitly add the cache validation header, such as If-None-Match or If-Modified-Since in your request, as NSURLSession will automatically add those header is needed.

For UIWebview, in addition to the default ios cache management NSURLCache, it also has an additional in memory cache management. This in memory cache manager will add the conditional cache header if necessary. So When using NSURLProtocol to intercept the xmlhttprequest, you may see the additional headers are automatially added to the requests. In case server returns 304 to those requests, the UIWebView will automatically load the cached response from memory and return to xmlhttprequest callback method using the status code and data saved in the cached response. When loading cached response from the in memory cache, the default NSURLCached is not involved.

Tuesday, July 25, 2017

Get iphone/ipad Device Identifier from ITune

Get device id from xcode is quite easy, just open from Window->Device menu and select the device will get it.

Get device id from iTune on mac is little bit trick.
1. First open the iTune and select the device by clicking the device icon

2. Select Summary item on left side list, it will show the Serial number as shown below

3. the trick is clicking on the "Serial Number" label field, and it will show the device id as UDID


Sunday, July 23, 2017

Android Studio editor shortcut keys on mac

Auto Format:
Command+Option+L

Comment/Uncomment toggle:
Command+/  (/ is forward slash)

go back to last visited line
Command +[

Go forward last visited line
Command + ]

Indent 
<TAB>

Reverse Indent
Shift + <Tab>

Show and add the override methods from the base class
Control + O

If a class name does not import the package automatically, edit the class name to trigger the auto import.

Friday, July 21, 2017

Fix gradle error when building old version of kapsel (SP14) andorid cordova project

When building android cordova project (kapsel sp14) with new version of android sdk, after imported the project into android studio, a few gradle related build errors may be reported. The following steps can be used to fix the error:

1. Open module build.gradle, and remove productFlavors section as empty, this will avoid the NDK error
if (Boolean.valueOf(cdvBuildMultipleApks)) {
    productFlavors {
    }
}

2. in the same build.gradle file, update gradle version and gradle plugin version as the latest one
dependencies {
    classpath 'com.android.tools.build:gradle:2.3.3'
}


task wrapper(type: Wrapper) {
    gradleVersion = '2.14.1'
}


Another simple way to updating gradle and gradle plugin version is, after importing the project into android studio, select File->Project structure menu and then update the gradle and gradle plugin version the default one used by android studio.

Tuesday, July 18, 2017

Using cordova channel to handle javascript event

Cordova uses channel&subscribe to fire and handle event. This logic can also be used in application logic. The APIs are quite easy to understand, so using the APIs does not need to know the details of the implementation. Actually, the cordova channel&subscribe model can be used in a regular js file not be part of a cordova plugin.

The below is a sample to using cordova channel to create an event, subscribe to the event and fire the event.

In index.html
<button onclick="createevent()">create event</button>
<button onclick="subscribeevent()">subscribe event</button>
<button onclick="fireevent()">fire event</button>
<button onclick="unsubscribeevent()">unsubscribe event</button>



In js file
function createevent(sticky){
   var channel = cordova.require('cordova/channel');
   channel.createSticky('myevent');
}

function subscribeevent(event){
    var channel = cordova.require('cordova/channel');
    channel.myevent.subscribe(handler);
}

function handler(e){
    alert("event fired: " + e);
}

function fireevent(event){
    var channel = cordova.require('cordova/channel');
      channel.myevent.fire("my event obj");
}

Friday, June 16, 2017

ASP.NET: include javascript file in asp.net page

1. add the javascript file (for example, test.js) into the script folder

2. update BundleConfig.cs to create a new bundle for the js file
     bundles.Add(new ScriptBundle("~/bundles/test").Include(
                                  "~/Scripts/test.js"));


3. to add the js file for all views, update _layout.cshtml in <head> section
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
    @Scripts.Render("~/bundles/test")
</head>

or simple add the below line in any cshtml view
    @Scripts.Render("~/bundles/test")


Fiddler: Replay saved fiddler server response to repeat bug in Fiori client local testing

1. Setup Fiddler trace in customer site. Deleting and installing client app or reseting client app to delete all local cached response from device.

2. Start the client app with Fiddler trace enabled and repeat the issue, save the all session in fiddler trace after the testing

3. In local testing environment, open fiddler from local windows box, and open saved fiddle trace file

4. select and copy all session items and drag&drop to AutoResponder tab

5. url for certain requests may have "smphomebuster=" followed by a random timestamp string, in order to match those requests, delete the part after "smphomebuster=" and then delete the "EXACT:", and also select "Match only once" checkbox from the match rule as

6. select the checkbox of "Enable Rules" to start AutoResponder

7. from iOS device, connect to local Fiddler proxy and using the same fiori url to connect to start the app, and then repeat the same operation as customer does.

Note, not all session can be replayed properly, which needs more investigation


Thursday, June 15, 2017

SCP: testing app discovery service

1. From SCP trial account cockpit, select Services tab
2. Select Mobile Service -> App & Device Management tile
3. Select go to Admin console
4. click and open Applications item on list items on left
5. click Config App Discovery
6. add a new config by clicking "New Application Configuration" buttton

In order for fiori client to get configuration from application discovery service, the Config App Discovery setting in Mobile Secure must have ":1.0" at the end of the app id, the app id is set in appconfig.js or fiori url.

For example: com.sap.fiori.client.debug:1.0

Tuesday, June 13, 2017

Test ios app ipa file using ad hoc provision profile and test fligh

1) Using Ad hoc
When using ad hoc for ios testing, the testing device does not need to install the provision profile, the only thing required from testing device side is adding the device id to the apple developer account device list.

The same certificate used for Apple store distribution can also be used for Ad hoc, so there is no need to create a separate distribution certificate.

1. Add the explicit app id for the testing app in apple developer account
2. create ad hoc provision profile in apple developer account, which associates the appid, and all devices that will be used for ad hoc testing.
3. from xcode project, disable "Automatically manage signing" and set the provision profile to the created one.
4. archive the project
5. export the archive for ad hoc distribution, the ipa file will be created in the output folder.

Note sometimes xcode will generate a generic xcode archive instead of ios archive, most likely that is due to a library project included in the workspace has a header section in build phase, please refer to
https://developer.apple.com/library/content/technotes/tn2215/_index.html#//apple_ref/doc/uid/DTS40011221-CH1-PROJ
for details. To workaround, just remove the library project and direct link to the library's .a file.
In addition, if forgetting to set the app version number will also cause this issue.


2) Using testing flight
When using testing flight, you only need the email of the tester, the device used for testing must use the appid with same email from the tester.

1. build the project and then archive the project. Be sure the certificate and profile are all valid otherwise the app may not install by Test Flight
2. create an app from Apple ITune Connect
3. Upload the Archive from xcode to iTune
4. In ITune, select Test Flight to add the tester with email or add the tester into a group
5. In Test Flight, select Build iOS and make the build ready for testing.
6. select the build for testing and add the tester group or individual tester.

Saturday, May 13, 2017

ios 10.3 test tip

1. If the app work in real device, but crash on ios simulator, then enable keychain share in target capability entitlement setting.

2. If the server self signed certificate is imported as email attachment, it is not accepted by iOS network connection by default. In order to trust the certificate for https connection, go to device settings->general->about->certificate trust Settings page, and be sure the installed server cert is trusted in there. 

SCP: Steps to use SAP mobile secure trial account for ios device management test

1. login your SAP Cloud Platform Cockpit using your trial account, select service App & Device Management.
2. Click Go to Admin console to open mobile secure admin portal
3. Select User->Manage Users, and edit the only user available
4. select action to edit, and change the user type from unmanaged to managed
5. Select Account->Device Setup, and then clickin iOS
6. In Apple MDM Certificate section, follow the step to download the csr request from the link, then upload to Apple site to get the certificate. Then upload it in step 3. A message box will indicate the operation succeeded.
7. Open Client App tab, to sign the ios afaria app.
  7.1 First create an explicit appid in your Apple developer portal web site, for example, com.sap.mobilesecure.me
  7.2 Create a development provision profile with the App ID and your development sign certificate, including all devices registered in apple dev portal
  7.3 Export your development signing p12 file from keychain
  7.4 Click Sign ios client button to Sign mobile Afaria client with above information
8. Create push certificate for sending push notification to Mobile Afaria
  8.1 From apple dev portal, create a push certificate for production with the same app id set in step 7.1
  8.2 download the push cert and import into keychain.
  8.3 export p12 file from keychain and click Install button to install it into mobile secure
9. from SAP Cloud Platform Cockpit using your trial account, select service App & Device Management, ang then Goto Mobile place
10. copy the url and open it from your ios mobile safari, the url should look like https://trial-i826633trial.sapmobileplace.com
11. follow the instruction to enroll the device
12. once it is done, it should start downloading Afaria app you just signed
13. You can also verify the push function by locking and unlocking the device from mobile secure device management.


Sunday, May 7, 2017

Which viewcontroller gets dismissed when calling iOS dismissViewController method

Apple document has the below information regarding the below method:
dismissViewControllerAnimated:completion: 
Dismisses the view controller that was presented modally by the view controller.

Discussion
The presenting view controller is responsible for dismissing the view controller it presented. If you call this method on the presented view controller itself, UIKit asks the presenting view controller to handle the dismissal.

This document is quite confusing as there are three view controllers are involved in the operation.
self  (this is the current viewController receiving the method call)
self.presentedViewController
self.presentingViewController

So in different cases, which view controller is really got dismissed?

A quick testing shows the actual logic is implemented as below:
When dismissViewControllerAnimated method is called on a ViewController object, it first checks whether the current viewControlller's presentedViewController contains a valid object, if so, then just dismisses the presentedViewController and returns.

If the current ViewController's presentedViewController is nil, then it will dismiss the current viewController. Internally by forwarding the message to its presentingViewController as Apple document mentions. 

So basically, the method will dismiss self.presentedViewController. If the self.presentedViewController is nil, then it will dismiss the current viewController which receives the method call.


Friday, April 7, 2017

Understanding Android fragment

Android fragment provides a way to reuse a UI component with the related UI layout and java event handling code.

The interface of a fragment to the outside world is the java class inherited from Fragment class. So when an activity includes a fragment in its UI layout, it includes a Fragment element indicated by the fragment's java class name in its layout xml file. At run time, when the activity's UI layout is inflated, it will initialize the fragment java class, which will load the fragment's own UI layout. As a result, Fragment only exposes to outside world as a java class, and its UI element detail is hidden from the outside. In this way, the logic and UI layout are enclosed inside the fragment's internal space.

The fragment's java class is also responsible to communicate to holding activity to handle the UI event, basically, it calls getActivity() to get the holding activity, and then calls the related method defined in the Activity class.

Similarly, dynamic fragment loading will add an instance of fragment java class by fragment manager transaction, which has a parameter to indicate where the fragment ui element will be added.

There is a unique usage of dynamic fragment to retain the state of the fragment after configuration change. By default, when device rotation changes, the activity and its holding fragment will be destroyed and re-created again. However, if setRetainInstance(true) is called on the fragment object in Fragment.onCreate method, then when device rotation changes, the fragment object will not be destroyed along with the activity, so it still hold its existing state. Note although the Fragment object is not created again, and onCreate is not called as well, the onCreateView is still be called so that Fragment can update its UI layout after the device orientation change. This feature only applies to fragment created dynamically.

@Overridepublic void onCreate(Bundle savedInstanceState){
  super.onCreate(savedInstanceState);
  setRetainInstance(true);
}

Thursday, March 30, 2017

ios cordova CDVLaunchScreen storyboard and splash screen plugin

When creating an ios cordova project with cordova (6.5.0), the xcode project generated by
cordova create
command will include a resource file "CDVLaunchScreen.storyboard". This storyboard has a image view in it, which refers to image resource "LaunchStoryboard", however by default, there is no image resource included in image.xcassets. Besides, the CDVLaunchScreen.storyboard is not referred or used in any places in the generated xcode project.

As a result, the cordova ios project still uses the old launchImage resource to display the app starting
splash screen as shown below


Usually, this does not matter for the application. However, if running the project on ipad pro device's UIWebView, it will shows the useragent is iphone instead of ipad. To demo this issue, update index.html and index.js with below changes and then run it on ipad pro device, the screen shot shows the user agent is IPHONE

Index.html
 <body>
        <div class="app">
            <h1>Apache Cordova</h1>
            <label id="useragent"></label>
            <div id="deviceready" class="blink">
                <p class="event listening">Connecting to Device</p>
                <p class="event received">Device is Ready</p>
            </div>
        </div>
        <script type="text/javascript" src="cordova.js"></script>
        <script type="text/javascript" src="js/index.js"></script>

    </body>

Index.js
   // Update DOM on a Received Event
    receivedEvent: function(id) {
        var parentElement = document.getElementById(id);
        var listeningElement = parentElement.querySelector('.listening');
        var receivedElement = parentElement.querySelector('.received');

        listeningElement.setAttribute('style', 'display:none;');
        receivedElement.setAttribute('style', 'display:block;');
        document.getElementById('useragent').innerHTML = navigator.userAgent;
        console.log('Received Event: ' + id);
    }



One way to workaround the usage agent issue is from the xcode project's General setting page, set "Launch Screen File" to "CDVLaunchScreen.storyboard".  Even if no image resource is available, the user agent for ipad pro will show the correct value as below


Note in the xcode project, before the "Launch Screen File" is set to "CDVLaunchScreen.storyboard", if the application is already deployed to the ipad pro device, then after the configuration change, the useragent will still show as IPHONE in the testing. The reason is cordova stores the useragent into ios UserDefault, so it will load the saved value as webView's useragent. 

In order to update the user agent after changing the "Launch Screen File" setting, the bundle version needs to be updated. In the project's General settings screen, the Identitiy Build number (CFBundleVersion in info.plist file) needs to be set to a new version to reset the saved user agent value. After the version is updated, the project will show the right ipad user agent on ipad pro device.

Cordova Splash Screen plugin
Although manually updating the xcode project setting, and assign the launch image resource for LaunchStoryboard image view is fine, the same function is also available in cordova ios splash screen plugin:
https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-splashscreen/

Basically, in cordova config.xml, under platform ios section, you will need to add the below splash items
    <platform name="ios">
        <allow-intent href="itms:*" />
        <allow-intent href="itms-apps:*" />
        <splash src="res/screen/ios/Default@2x~universal~anyany.png" />
        <splash src="res/screen/ios/Default@2x~universal~comany.png" />
        <splash src="res/screen/ios/Default@2x~universal~comcom.png" />
        <splash src="res/screen/ios/Default@3x~universal~anyany.png" />
        <splash src="res/screen/ios/Default@3x~universal~anycom.png" />
        <splash src="res/screen/ios/Default@3x~universal~comany.png" />
    </platform>

And then add those image resource png files into projectRoot/res/screen/ios folder, the image size should follow the below table

filename size width height
Default@2x~universal~anyany.png 2732x2732 any any
Default@2x~universal~comany.png 1278x2732 com any
Default@2x~universal~comcom.png 1334x750 com com
Default@3x~universal~anyany.png 2208x2208 any any
Default@3x~universal~anycom.png 2208x1242 any com
Default@3x~universal~comany.png 1242x2208 com any

Once the resource is added and config.xml is updated, then add the splash screen plugin into the cordova project. You will also need to run 
cordova prepare 
to make the CDVLaunchScreen.storyboard set to the "Launch Screen File" field, you can verify this is set properly by checking the xcode project's genearl settings.

With this change, the ipad pro UIWebView should show its user agent properly as ipad.

For more information, please refer to cordova splash screen pluguin at

Build jar file using java sdk 1.7 on mac for android studio library

Currently by default, java SDK 1.8 will be installed on mac for compile java code. However, a jar file compiled using java sdk 1.8 cannot be used by Android studio (2.3) as library project. The android project will build without error, but it will fail when trying to debug the app on device or simulator. Set SourceCompatiblity Setting in app's build.grade file also does not help.

One way to solve the issue is building the jar file with java sdk 1.7.

To do so, first download Java SE sdk 1.7 from oracle web site, and then install it on your mac. You do not need to first uninstall Java 1.8 from your mac, the new java 1.7 will be installed on the mac, but it will not remove or change any settings used by the current installed java sdk 1.8.

After the installation is done, you can check there should have two sub folders under
/Library/Java/JavaVirtualMachines   
One for 1.8 and one for 1.7.

Now you need a way to switch the java sdk version from terminal app to build the java source code. The easy way to do so is add the below two lines in your .bash_profile
alias setJdk7='export JAVA_HOME=$(/usr/libexec/java_home -v 1.7)'
alias setJdk8='export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)'

After saving the change in bash_profile, then you can switch the java sdk version by typing the below command from terminal app:

setJdk7
set java sdk to 1.7

sdkJdk8
set java sdk to 1.8

With the above change, you can rebuild the jar file using jdk 1.7 and then import the jar file into the android studio, it should be able to build and run on device and simulator without problem.

Note: when adding new jar file as a module in android studio project, the settings.gradle will be updated to include the jar file. you will need manually add the new module in app's build.gradle's dependency section.

Sunday, March 26, 2017

Upgrade to Android Studio 2.3 on mac may fail to open existing or create new android projects

Just upgrade Android studio 2.3 on mac, and after that, the android studio cannot open the old android project or create new android project. Basically it only recognizes those android mobile projects as java desktop projects

The reason is somehow, during the upgrading, the android Support was disabled in android studio Preferences's Plugin settings. You will need to manually enable it from Preferences' Plugins screen, and then restart the android studio, and follow the prompt.



Hopefully the other functions in Android Studio 2.3 are not as fragile as this.

Friday, March 17, 2017

Steps to localize ios app in xcode including storyboard, plist file, code

In xcode 8, the internationalization setting is in the project (not target)'s info settings, which is shown below.


Step 1: localize string in storyboard
1. Be sure the "Use Base Internationalization" checkbox is checked
2. Check the storyboard to delete all unused string, otherwise they will appear in the localized string files
3. click the language + button to add a new language, like Chinese, which will create new string file for the added language under the storyboard folder. This file contains all the strings used in the storyboard. Translate the string to the proper language.
4. Change the device language to the new language, and restart the app, the localized string should show in the storyboard UI elements


Step 2: localize strings in plist file
The second step is localized the application display name as well as other strings defined in plist file
1. from the xcode's File->New->File, select Resource tab, and create a string file, the file name is
InfoPlist, (the full file name is InfoPlist.strings)
2. select the InfoPlist file, and in xcode identity inspector window, click localization button, and then check the language you want to localize for the plist file
3. Open the localized InfoPlist file as property list, and add the key value pairs you want to localized, the key must match the keys defined in the original info.plist file, for example, to localize the display bundle name, add the below item
Note you only need to add the key/value pairs in localized InfoPlist file, and there is no need to make any change in InfoPlist file in base.lproj folder, as if the value cannot be found in there, it will use the value defined in the info.plist file.
Likely, only bundle display name and feature usage permission description (like NSCameraUsageDescription etc.) need to be localized in info plist file
After the display bundle name is changed, test it on device, the application name shown in the device home screen should have the localized name.

Step 3: localize hardcoded string in source file
1. go through the swift source files and identify all the strings that will appear in the UI, replace the hardcoded string by calling NSLocalizedString method with any comment  as below
self.navigationItem.title = "New Password"
to
self.navigationItem.title = NSLocalizedString("New Password", comment: "New Password screen title")

The comment parameter does not matter, and it will only show as comment in the string file generated in the last step. Actually, just set it to empty string "".

2. from terminal app, goto the application project folder, and run the below command:
find . -name \*.swift | xargs genstrings -o .
This will generate a file localizable.strings under this folder
3. add the generated file into xcode project, 
4. select the file from xcode identity inspector, and click localization button to localize it for different language
5. translate the localized files for all the keys
6. test the app under different language and verify the ui is updated to different language settings

Saturday, March 11, 2017

Steps to convert ios UITableViewController to UIViewController

UITableViewController is easy to create, but hard to change. It cannot add any additional view, such as toolbar or Google AdMob view. It is best practice to not use UITableViewController at all from the very beginning.

For existing UITableViewController, the below steps can be used to convert it to UIViewController with a UITableView

1. open storyboard, and add a new UIViewController,
2. select the old tableview object, and press option button and drag-drop it to the new viewcontroller. This will copy the tableview to the new UIViewController
3. Optionally, if the new UIViewController belongs to a navigation controller, then after creating the segua, then add a navigation item to the new UIViewController to customize the viewcontroller title, and then add bar button item to customize the left and right navigation bar button item
4. In the original uitableviewcontroller source file, in the class definition,
replace 
UITableViewController 
with
UIViewControllerUITableViewDataSource, UITableViewDelegate
5. go back to storyboard editor, and change the new UIViewController's class to your class name
6. copy the storyboard id and restoration id to the new UIViewController
7. drag drop the tableview to your viewcontroller source file to create a outlet for the tableview, and name the outlet to tableView, so it has the same name as UITableViewController.
8. From storyboard, select the UIViewController tableview, and then open connection inspector, delete the datasource and delegate outlets. And then create a new outlet to the new UIViewController
9. check other action and outlet from the old UITableViewController storyboard and create the same one on the new UIViewController
10. In the UIViewController source file, if UITableViewController only uses dynamic cell, then simply fix the compile error by deleting the override keyword in the related function.
If the original UITableViewController uses static cell in storyboard, then it is more complex. First changing the storyboard cell to dynamic cell, and then creating the derived table cell class for each table cell type. Then implementing the datasource delegate method to provide the row and section information, finally, when creating each tablecell row, associate the table cell with the existing outlet variable.
11. build and run the project to verify the function

Steps to replace apple iAd to google adMob in xcode project

Since apple stops support of iAd, so existing ios apps published in app store need to be updated to using google adMob to earn ad income.

The below steps are how to make the switch:
1. open xcode project remove iAd framework and any related code
2. install cocoapod on mac if not yet installed before
3. register to google adMob account at
https://apps.admob.com/#home
4. from adMob home screen, select Mobilize tab, and click the "mobilize new app" button. Then select the app store app you already published and create an adMob app.
5. In step 3, create a google firebase project.  it will give you a link to download the googleservice-info.plist file. The config file is also available in firebase web page at
https://firebase.google.com
6. cd into xcode project and create pod file as below and be sure to update the target to the right one
source 'https://github.com/CocoaPods/Specs.git
platform :ios, '7.0'

target 'YourProjectTarget' do

  use_frameworks!
  pod 'Firebase/Core'
  pod 'Firebase/AdMob'

end
7. run pod update to add the firebase framework into your xcode project
8. open the newly created xcode xcworkspace from xcode
9. add the previous step 5's plist file into the project
10. update xcode project storyboard to add ad view as described at
https://firebase.google.com/docs/admob/ios/quick-start
Basically, you will need to add UIView in the viewcontroller, and put the view at the bottom of the viewcontroller, and then update the viewcontroller code to made request to server to get the ads
11. To avoid the compile error, add the below import to the related source file

import GoogleMobileAds
import UIKit
12 In case you get a wired error of fail to run the app on real ios device, try http://jonathanblog2000.blogspot.ca/2017/03/ios-xcode-fix-framework-not-load-with.html
13. check appdelegate.swift to have the right admob appid, and all view controller to have right ad unit id.

After a while, when you build the ios project, you may get a warning of
"Please consider updating your SDK to the most recent SDK version to get the latest features and bug fixes."
In order to update the google sdk version, from mac terminal, goto the project folder and run
pod update
The above command will update google sdk to the latest version, and avoid the version update warning.

Wednesday, March 8, 2017

ios & xcode: Fix framework not load with error of "code signing blocked mmap()"

When running ios project on ios real device, the app may crash with an error of "code signing blocked mmap()". Although the same project can run properly on simulator.

After some testing, figure out the issue is related to the Apple Signing certificate. To fix the issue, first delete all Apple signing cert from keychain. Then open Xcode preference/Account, select the Apple ID, and the right team. Click view details, and reset button for ios development in signing identity section.

This is the only method that fixes the error in my test.

Tuesday, March 7, 2017

Avoid the annoying step to trust enterprise apple dev certificate each time after installing a new app

A recent change from Apple requires developer to trust the enterprise developer certificate each time when installing and run a new app on ios device.

Basically, when a new app is installed on device signed with enterprise dev cert, developer has to go to device settings\general\profile page to trust the cert first before able to debug or run the the app. Once the app is deleted and installed again, the dev has to repeat the same trust step again.

One way to avoid this is explicitly install the dev cert on device:
1. open xocde preference menu, select Account tab, and select the appid for enterprise dev account
2. from the team section select the enterprise team
3. click view detail button.
4. in the detailed dialog box signing identigty section, select "ios development" item. right click it and export
5. send an email with the exported dev certificate to your ios device, and install the cert on your ios device.
6. The enterprise dev cert will be trusted and no need to explicitly trust it again anymore.

Wonder why apple could not add a "DO NOT BOTHER" button on the cert trust page.

Friday, March 3, 2017

Set wifi proxy bypass list on android device

Android device does not have a good support on proxy auto config, so it is necessary to manually set the proxy settings.

Host:
proxy.phl.sap.corp

port:
8080

Bypass proxy for:
*.sap.corp

note the pass proxy list needs to have *, so that it will bypass the host of https://smpsdk-ciat-rel.mo.sap.corp:8081/, if * is missing, then the bypass will not work.


Steps to set proxy on android simulator
1. launch android simulator
2. open settings app from simulator
3. in Wireless & networks section, click "More..." and then select "Cellular networks" item
4. click Access Point Names item
5. click T-Mobile US item
6. set proxy server name in "proxy" field, for example: proxy.phl.sap.corp
7. set proxy port in "port" field, for example: 8080
8. click top right three point menu item and then select save
9. test with mobile chrome and you should be able to access www.yahoo.com
(not yet find a way to set bypass proxy server list on simulator)