Sencha Touch is a technology that can be used to take a Sencha Touch (JavaScript) based application, and generate platform specific binaries used to run that mobile application. This means that you write a single JavaScript based mobile application, which gets turned into an Android APK file and an iOS IPA file or anything else you may need. The issue has been getting this to reliably work for more than one project, on more than one platform, on more than one computer, and without human intervention. This is the story…
Using the following technologies and versions, which will be explained more in detail later:
There are 3 fundamental approaches for dealing with building Sencha Application
The way this works is that a lot, and I mean a lot of files and directories get created and modified when you do the native sencha build. The problem comes with knowing what gets generated, what gets modified, and what has to be modified manually by hand and under what conditions.
cordova
Everything under “cordova” gets generated, but only the first time. The difficulty comes with what happens in the following cases:
“Sort of Cordova”
What you have to do in the above circumstances depends on which of the three Cordova approaches you are using. As it turns out, using the “Sort of Cordova” approach results in the least amount of manual and/or automated work. In particular the “Always Cordova” approach where you try and keep the Cordova configuration results in a nightmare of updating configurations under specific conditions. It took the above directory and file structure research in order to make an attempt at handling configuration updates, which resulted in a lot of complexity.
The resulting project structure looks like the following (when running Sencha Touch as a Dynamic Web Project in Eclipse):
MyProject
What about “Never Cordova”?
The “Never Cordova” and “Sort of Cordova” approaches share the same fundamental principle of always generating the Cordova configuration from scratch, and the differences where the “cordova” directory is generated. In the “Sort of Cordova” approach the “cordova” directory is kept in the Sencha Project and just uninstallad/ignored, versus copying everything to a temporary location. The problem with the “Never Cordova” approach is that copying all of those files is time consuming, and generally adds 30-60 seconds onto the build process.
Are you sure?
No, but based on the upcoming description of processes the “Sort of Cordova” approach ended up being the least amount of build/scripting code and related complexity. I know this because I implemented all three approaches at one point, and each one has its upsides and downsides. If there is a better way that works to do this I would be eager to hear it.
Before I launch it to what will be a mind-numbing description of what has to be handled in the build process, it is best to consider what the goals were:
There are several things that should be used as dynamic inputs to the process, so that a single build process can be used for multiple environments and under multiple conditions:
Before you do a build, delete the directory that is to contain your binary output. With all of the zipping, unzipping, and other activities going on this is done to ensure you are getting an accurate binary.
Checking prior to running the build that the environment has all the appropriate stuff installed will save you and future developers a lot of time. There is nothing more frustrating that chasing down silly errors that were the result of not having the correct version of Cordova installed.
It is also a good idea here to establish what OS you are running on, so you know how to interact with the command line. This is because you will be running stuff at the command line a lot.
Windows
Command: cmd
Command Switch: /c
Not Windows
Command: /bin/sh
Command Switch: -c
This matters because this determines how you will call the various sencha and cordova commands, for example:
Windows> cmd /c sencha app refresh
Not Windows> /bin/sh –c sencha app refresh
It is possible for things to work in sencha in the browser that do not work once they are on the mobile device. So far the one I have into the build process is checking for is how to navigate through a controller.
Bad: this.getController
Good: this.getApplication().getController
Any .js file that contains the following text, should be a reason for failing the build process: this.getController
Accessing a controller in this manner will not work on iOS or Android.
Verify that Cordova 3.0.0 is installed. Why? Anything newer than 3.0.0 doesn’t seem to work, or at least I couldn’t get it to work.
After spending a lot of time trying to chase down why I couldn’t get a sencha native build working, I finally narrowed the issue down to Cordova by looking at all the Cordova commands Sencha was running behind the scenes, and then took Sencha out of the equation by using Cordova 3.3.0, 3.1.0, and 3.0.0 directly:
cordova create HelloWorld com.example.hello “HelloWorld”
cd HelloWorld
cordova platform add android
The adding of the android platform would fail every time for Cordova 3.3.0, 3.2.0, and 3.1.0 with a nodejs.js compile error. I tried this on three different computers (2 Windows machines and a Mac) and got the same result. After losing more time to trying to Google my way out of the issue, I ended up settling on Cordova 3.0.0 since it actually worked in this test, and allowed me to do a native build in sencha.
The result is that a build should run the following command to verify that Cordova 3.0.0 is installed:
cordova -v
If the result doesn’t contain “3.0.0”, abort.
Sencha Command is the tool that is used to wrap most of the build process, and is used in various forms throughout it. Various versions of Sencha Command exist, but the only ones that will work with the latest Sencha Touch 2.2+ projects is 4.0.1.
For this reason, the following command should be used to verify the version of sencha command in use:
sencha which
If the result does not contain “Sencha Cmd v4.0.1”, abort.
Depending on what you building, you should verify that the platform specific build tool is available and is of the correct version.
Android – The Android SDK works with every version I have tried, just verify it is installed”
adb version
If the output doesn’t contain “Android Debug Bridge”, abort.
iOS – If you have Xcode5 installed it comes with xcodebuild:
xcodebuild –version
If the output doesn’t contain “Xcode 5.0”, abort.
The sencha touch framework is a lot of files. Why does this matter? It matters because it takes a lot of time to check-out (and initially check in) these files. This can significantly slow down clean builds from a continuous integration perspective. In order to speed this up I have been zipping the Sencha Touch framework, and extracting it when needed at build time to a location that is ignored by SCM.
The Sencha Touch framework looks like the following:
touch
Example Process:
This is a result of the decision to follow the “Sort of Cordova” process, where we don’t store the various Cordova generated files in SCM. In order for this to work, we have to uninstall Cordova prior to doing a build.
This is done by running the following command from the WebContent directory:
sencha cordova remove
The result is the following:
Installing Cordova on a project results in the creation of the WebContent/cordova directory, and all of the various Cordova configuration files and modification to Sencha configuration files.
This is done by running the following command using Application ID and Application name from the WebContent directory:
sencha cordova init some.app.id SomeAppName
The result is the following:
An important note here is that unless you are specifically using the sencha command to build from packager.json, this file and most of its contents are apparently ignored. There is a Sencha command to generate an initial packager.json, however from a build perspective this is something that if you are using that needs to be configured on the fly. Note that in my build process I use sencha app build native instead of the other option to build using package.json. From that perspective, the contents of packager.json look like the following and the items that are used need to be generated with each build:
{
“applicationName”:”SomeAppName”,
“applicationId”:”some.app.id”,
“bundleSeedId”:”FGSHGFHSD”,
“versionString”:”1.0″,
“versionCode”:”1″,
“configuration”:”Release”, // NOT USED
“platform”:”Android”,
“deviceType”:”Universal”, // NOT USED
“certificatePath”:”android-keystore“, // NOT USED
“certificateAlias”:”android-alias”, // NOT USED
“certificatePassword”:”Foobar”, // NOT USED
“provisionProfile”:””, // NOT USED
“notificationConfiguration”:””, // NOT USED
“sdkPath”:”C:/adt-bundle/sdk“, // NOT USED
“androidAPILevel”:”18″, // NOT USED
“icon”: { // NOT USED
“36”:”resources/icons/Icon_Android36.png“,
“48”:”resources/icons/Icon_Android48.png“,
“57”:”resources/icons/Icon.png”,
“72”:”resources/icons/Icon~ipad.png”,
“114”:”resources/icons/Icon@2x.png”,
“144”:”resources/icons/Icon~ipad@2x.png”
},
“inputPath”:”./”, // NOT USED
“outputPath”:”../build/”, // NOT USED
“permissions”:[ // Goes into AndroidManfest.xml, but overridden
“INTERNET”,
“ACCESS_NETWORK_STATE”,
“CAMERA”,
“VIBRATE”,
“ACCESS_FINE_LOCATION”,
“ACCESS_COARSE_LOCATION”,
“CALL_PHONE”
],
“orientations”: [ // NOT USED
“portrait”,
“landscapeLeft”,
“landscapeRight”,
“portraitUpsideDown”
]
}
Why aren’t you going the build thing that involves packager.json?
Several reasons:
In order for to be able to use Cordova plugins, WebContent/app.json has to be modified to set remove to false.
As a precaution the build process should just replace any instance of remove=”true” to remote=”false”.
This file is later modified and copied into several other locations involving platform specific www directories.
Why do you have to worry about updating plugins? Because cordova add plugin is broken. Consider this example of me trying to execute it from the WebContent directory (where cordova is installed):
cordova plugin add https://git-wip-us.apache.org/repos/asf/cordova-plugin-geolocation.git
[RangeError: Maximum call stack size exceeded]
Yes, GIT is installed and a tried to Google my way out of this one as well.
As a workaround I did the following:
The result is that when you later run the native builds, the related plugin configurations will get generated and embedded into the downstream native projects.
This also means that if you don’t have a zip file that corresponds to something in WebContent/plugins, you have a plugin that is only for your project that you can update by editing the code that sits in WebContent/plugins. You now have two ways to deal with plugins:
However if you want to update with a new zip file, you would have to delete the corresponding out of WebContent/plugins so the new source would get extracted there.
Most mobile applications are going to have some embedded properties that do things like specify what URL they are going to need to talk to. In Sencha Touch this can be done by putting a properties file at WebContent/Application.properties, which contains things like the following:
serviceUrl=http://foo/bar/url
appId=SomeAppId
environment=test
At Runtime app.js can read in these properties and store them so they are globally accessible. This avoids the need to have to keep this information in code and/or manually modify them for each environment.
In the project I specify an environment directory that contains property files for each environment, for example:
As a part of the build process, I specify a “properties” parameter that indicates which property file to use, and then use that file to overwrite the contents of WebContent/Application.properties.
There are a lot of icons in various sizes that are used for the application icons on specific platforms. By default there are all Sencha graphics located in WebContent/resouces/icons. I have made a list of the required icons names and sizes for iOS and Android and where they need to go:
As it turns out though, none of this is used. I don’t know whether it is some other build settings or because I am not using the packager.json based build. These icons don’t make it into the generated native applications.
Manually adding the platform refers to the command “cordova platform add X”, where X is the name of your platform like ios or android. The issue is that you should not have to run this command at all, because “sencha app build native” will take care of doing this if needed. Why do I run this command then? Because “sencha app build native” doesn’t always successfully add the required platforms.
The issue I had was with a Windows Server 2012 box where “sencha app build native” would run the “cordova platform add android” command and it was pass, but the WebContent/cordova/platforms/android directory would not get created. If I went into WebContent and ran “cordova platform add android” the directory would get created correctly though. This worked on all of my other machines, looking at the –debug output of sencha did not yield any results, so I went ahead and made the build process add the platform prior to attempting the native build.
Android
cordova platform add android
iOS
cordova platform add ios
The result is that when in the future step that “sencha app build native” is run it will skip the attempt to add the platform.
The need to manipulate configuration files is the direct result of the “Sort of Cordova” approach. Since we don’t store Cordova configuration files you can’t modify the platform specific configurations files which are:
This is the biggest downside to the approach, but as pointed out earlier if you attempt to store the Cordova configuration (even it parts), you end up with a lot of problems when you need to make changes.
The types of changes you need to make depend on the platform.
Android: AndroidManifest.xml
Example Result:
<?xml version=‘1.0’ encoding=‘utf-8’?>
<manifest android:hardwareAccelerated=“true” android:versionCode=“1” android:versionName=“0.0.1” android:windowSoftInputMode=“adjustPan” package=”some.app.id.SomeAppName” xmlns:android=“http://schemas.android.com/apk/res/android”>
<supports-screens android:anyDensity=“true” android:largeScreens=“true” android:normalScreens=“true” android:resizeable=“true” android:smallScreens=“true” android:xlargeScreens=“true” />
<application android:debuggable=“true” android:hardwareAccelerated=“true” android:icon=“@drawable/icon” android:label=“@string/app_name”>
<activity android:configChanges=“orientation|keyboardHidden|keyboard|screenSize|locale” android:label=“@string/app_name” android:name=“AppName” android:theme=“@android:style/Theme.Black.NoTitleBar”>
<intent-filter>
<action android:name=“android.intent.action.MAIN” />
<category android:name=“android.intent.category.LAUNCHER” />
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion=“10” android:targetSdkVersion=“18” />
<uses-permission android:name=“android.permission.CAMERA” />
<uses-permission android:name=“android.permission.VIBRATE” />
<uses-permission android:name=“android.permission.BLUETOOTH” />
<uses-permission android:name=“android.permission.VIBRATE” />
</manifest>
iOS: AppName-Info.plist
Example Result:
<?xml version=”1.0″ encoding=”UTF-8″?>
<!DOCTYPE plist PUBLIC “-//Apple//DTD PLIST 1.0//EN” “http://www.apple.com/DTDs/PropertyList-1.0.dtd”>
<plist version=”1.0″>
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleDisplayName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string>icon.png</string>
<key>CFBundleIcons</key>
<dict>
<key>CFBundlePrimaryIcon</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>icon.png</string>
<string>icon@2x.png</string>
<string>icon-72.png</string>
<string>icon-72@2x.png</string>
</array>
<key>UIPrerenderedIcon</key>
<false/>
</dict>
</dict>
<key>CFBundleIdentifier</key>
<string>some.app.id.SomeAppName</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>62</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSMainNibFile</key>
<string></string>
<key>NSMainNibFile~ipad</key>
<string></string>
<key>UISupportedExternalAccessoryProtocols</key>
<array>
<string>some.protocol.name</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
Another reasonable approach would be just to store the AndroidManifest.xml and AppName-Info.plist files, copy them into the appropriate locations prior to native building, and just update the version information.
If you want to use your own icons (for Android and iOS) applications you are going to have to move them into the derived locations yourself.
For a reason I have not been able to determine the application once native requires Dom.js, which is inside the Sencha source code. Yes, I have tried using both sencha-touch-all.js and sencha-touch.js in app.json. The way I was able to work around this was by copying the Sencha touch source into the www directories in the native projects.
FileNotFoundException: www/src/event/publisher/Dom.js
The reason this is noteworthy is because you can’t directly copy files there, because the Sencha build process will delete them. In order to get WebContent/touch/src into the native www you have to copy it to the .staging directory.
Android:
Copy WebContent/touch/src into WebContent/cordova/platforms/android/.staging/www/src
Results in WebContent/cordova/platforms/android/assets/www/src
iOS:
Copy WebContent/touch/src into WebContent/cordova/platforms/ios/.staging/www/src
Results in WebContent/cordova/platforms/ios/www/src
There are apparently two ways to produce a native build in Sencha Touch:
sencha app build native
This produces a mostly debug version of whatever platforms you are building. I say “mostly” debug because configurations that can be passed in to do some things like a production release, others require intervention.
sencha app package build <configFile.json>
This is supposed to produce a signed native binary for whatever platforms you are building. I say “supposed to” because I was never able to get it working, while sencha app build native was relatively easy to get to function.
There were several reasons why I didn’t go with this solution:
The result is I run the following command from the WebContent directory:
sencha app build native
This is where you get into things that you have to do because of how you build the native Android binary, and because of what you need to do on iOS to turn an app into an ipa.
Since we produced a debug version of the APK, we have to unsign it, and then sign it again with our key. This is done in the following steps:
The result is that you have two apk files: debug and signed
…but the jarsigned prompts me with a password? I used an Expect script to automate it.
On iOS you have to the AppName.app file into the signed IPA files that you need, which come in three varieties:
There are enough steps here that I have the build process call a script that is used to do this building.
Prerequisites:
Steps for building
An issue with xcodebuild however is that is sometimes will fail and not return a failing exit code, the result is that it failed but you don’t know about it. As a workaround I parse the output for “error generated” and if present fail the build.
Other ways of doing it:
Now that you have built the various binaries, you know have to find them.
The laziest way to do it is to look for **/*.ipa and **/*.apk files within WebContent while excluding **/*unaligned to ignore some of the Android build step results. On Android you should have a signed APK and a Debug Signed APK, while on iOS you should have an IPA for every certificate you used.
Also note that along with the binaries, you also will have corresponding native projects.
Eclipse Android Project (without a .project and .classpath): WebContent/cordova/platforms/android
iOS Xcode 5.0 Project: WebContent/cordova/platforms/ios
If you want to do native debugging you can use these projects, and you can also generate them without the binaries by stopping after Step 16.
(http://jvalentino.blogspot.com/2014/02/automating-building-native-android-and.html)