Author Archives: skypanther

Rebuilding modules for Titanium 6

I’m no modules expert, nor am I native Android guru. But, when your app depends on eight or ten native modules and Appcelerator announces breaking changes in their SDK, sometimes you have to just dive in and give it your best shot.

Appcelerator has released the beta version of Titanium 6. With it, they’ve updated the V8 engine used on Android. This promises us greater performance and various bug fixes. But, it also means that all native Android modules you use in your app must be updated and recompiled with Ti 6 tooling.

I’ve been going through the modules our app uses. So far, it has been mostly smooth. For those of you facing a similar situation, I thought I’d share my notes of what I found was needed for this process.

Here are the steps I’ve had to take so far:

Update the module’s manifest file:

  • Bump apiversion from 2 to 3.
  • Remove “armeabi” ABI from listing (keep “armeabi-v7a”).
  • Update minsdk to 6.0.0.v20161017194738 (you’ll set this to 6.0.0 once the SDK goes to GA)
  • Bump their module version (typically bump the major number since this is a backwards-incompatible change).

Update the module’s build.properties file:

  • titanium.platform must point to the 6.0.0.v20161017194738 TiSDK location (again, use the 6.0.0.GA version once it’s available)
  • make sure the API/Google API levels are set to something like 23
  • you’ll want to use android-ndk-r10e NDK or newer (see my notes below for my build environment)

Code changes

You will need to remove all references to TiContext, as that has been removed from the SDK. So far, the only references I’ve seen have been in overloaded constructors. In those cases, I simply deleted those constructors as well as the import statements.

Depending on the age of the codebase you’re working with, you may also have to update a few import statements. I’ve found these class changes in the modules I’ve updated:

  • org.appcelerator.titanium.util.TiConfig is now org.appcelerator.kroll.common.TiConfig
  • org.appcelerator.titanium.util.Log is now org.appcelerator.kroll.common.Log

You might find it helpful to reference some of the modules that Appcelerator has updated to see the sorts of changes they’ve made. For example:

My build environment

Finally, just for reference, this is my environment. You can probably use other versions of all these components.

  • Ti SDK 6.0.0.v20161017194738
  • Appc CLI 6.0.0-61
  • Node 4.2.4 (newer would probably fine, as this is the min supported version)
  • Android APIs / Google APIs: 23
  • NDK android-ndk-r10e (version r11c should also be compatible, I just haven’t updated yet)

A big thanks to @hans on TiSlack for helping me figure out the above.

Apache userdir with El Capitan

I spent four hours working through guides like this one and lots of Stack Overflow posts trying to get userdir support working again after updating from Yosemite to El Capitan. (You know, so that I can open http://localhost/~myusername and put files in /Users/myusername/Sites.)

Eventually I gave up and used a simple workaround. In Terminal:

cd /Library/WebServer/Documents
sudo ln -s /Users/yourusername/Sites yourusername
# e.g.
# sudo ln -s /Users/timpoulsen/Sites timpoulsen

Of course, I have to remember to not put the ~ in the URLs. But what’s nice is there’s no need for sudo to add/remove files from my Sites folder like there is with /Library/WebServer/Documents.

Localhost user dir hack

Simulating a bad network connection on iOS devices

Following yesterday’s quick Android tip, here’s a quick iOS tip.

Want to see how your app performs over a crappy network connection? You could tell the boss you gotta go on a field trip. Or, do this:

Open Settings and tap Developer Options, tap Status under the Network Link Condition item. From there, turn on the conditioner and select the type of network connection you want to simulate.

iOS Settings app Developers tools page of Settings Network Link Conditioner settings

Then again, a field trip to your local coffee shop is probably more fun.

Arggh! Failed Android install

You just spent 10 mins waiting for Titanium to build your app for Android and install your app. At the end of it all, you get a message like the following in the console:

[INFO]  Making sure the adb server is running
[INFO]  Installing apk: /path/to/your_app/build/android/bin/YourApp.apk
[INFO]  Installing app on device: Galaxy Nexus
[ERROR] Failed to install apk on "01498FE710016018"
[ERROR] Error: INSTALL_FAILED_VERSION_DOWNGRADE

There are all sorts of reasons that an install will fail (in the case shown here, a newer version of the same app is already installed on the device…in development, it happens). You could fix the problem then wait for Titanium to rebuild your app again. Or, turn to adb to install the version that’s already compiled. The console log even helps you out giving you the path to that APK file.

~ adb install -r /path/to/your_app/build/android/bin/YourApp.apk
4134 KB/s (26912556 bytes in 6.355s)
    pkg: /data/local/tmp/YourApp.apk
Success

(The -r flag says to reinstall the app, and is not really necessary here. But it doesn’t hurt so I usually throw it in there.)

The adb command is included with the rest of your Android tools. It’s handy for all sorts of things: accessing device logs, transferring files, and even installing apps as shown above. Check the adb docs for more info.

Multiple exceptions for iOS 9 App Transport Security

As you’ve surely heard by now, Apple is requiring secure network connections (https). This requirement will apply to all newly-submitted apps for iOS 9, as well as updates to existing apps submitted after the release of iOS 9. There are some great articles about why this is good and how you should be using it. What I wasn’t able to find was clear documentation of how to enter multiple exceptions. So, just for the record, this works:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>  
        <key>myinsecuresite.com</key>  
        <dict>  
            <key>NSExceptionAllowsInsecureHTTPLoads</key>  
            <true/>  
            <key>NSIncludesSubdomains</key>
            <true/>
        </dict>  
        <key>myotherinsecuresite.com</key>  
        <dict>  
            <key>NSExceptionAllowsInsecureHTTPLoads</key>  
            <true/>  
            <key>NSIncludesSubdomains</key>
            <true/>
        </dict>  
        <key>localhost</key>  
        <dict>  
            <key>NSExceptionAllowsInsecureHTTPLoads</key>  
            <true/>  
        </dict>  
        <key>127.0.0.1</key>  
        <dict>  
            <key>NSExceptionAllowsInsecureHTTPLoads</key>  
            <true/>  
        </dict>  
    </dict>  
</dict>

In the Apple forums, there was a note that at least in pre-GM versions of iOS 9, IP address exceptions were not working. I have not tested this. But supposedly they’re supported using the format shown above.

The right and wrong way to save file paths in Titanium

If your app requires you to save files, perhaps photos that the user snapped using your app, your first thought might be to do something like:

// don't do this:
var file = Titanium.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory, filename);
file.write(imageBlobData);
// saving the full nativePath to the DB
myModel.set('filepath', file.nativePath);
myModel.save();
console.log(file.nativePath);

Unfortunately, you cannot count on the nativePath remaining constant between app versions. Run this sort of app on iOS, check the console output, and you’d find:

// with the first run of the app:
/var/mobile/Containers/Data/Application/551B24CF-1186-4DDC-B31D-19C03DA2A19D/Documents/myfile.jpg
// after updating the app:
/var/mobile/Containers/Data/Application/47A737A9-D284-43FA-AF1C-FD24872C88EE/Documents/myotherfile.jpg

The app’s GUID has changed and your stored file path is no longer valid. While the structure of Android file paths are different, the same general problem exists on Android as well. This means that if you had some place in your app where you did the following, you’d have broken images due to invalid file paths after an update.

myImageView.image = myModel.get('filepath');

The old files are probably still there. Just your explicit reference to their location is now invalid. Instead, you should do:

// right way:
var file = Titanium.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory, filename);
file.write(imageBlobData);
// save only the file's name
myModel.set('filepath', filename);
myModel.save();
// then this will work even after an upgrade
myImageView.image = Ti.Filesystem.applicationDataDirectory + myModel.get('filepath');

Automating Titanium builds with Grunt

OMIGOSH, I submitted our app to Apple with it pointing to our development endpoints, not production! Stupid mistakes like this are the stuff of developer nightmares, and they can easily happen when you have manual steps in your release process. People screw up.

We use the awesome Installr service to distribute iOS and Android test builds. For iOS, those betas have to be ad-hoc builds. And, we send our test Android builds as production builds signed with our company’s certificate. Until Appcelerator resolves ALOY-1210 there’s no way to differentiate between ad-hoc and production builds using the built-in tooling.

Till now, we’ve manually edited the config.json file before each Installr or production build to list the correct endpoints under the env:production key. That’s a manual and error-prone process that was bound to fail, and did.

We already use Grunt to automate our Installr build process. Type grunt in the project directory and our Grunt script cleans the project, increments version numbers in the tiapp.xml, builds the app for iOS and Android, uploads to Installr, and sends out the release emails. Obviously, we should have been using Grunt to also manage configuration files.

I document the entire process of setting up grunt for a Titanium project in this Gist. Here’s the simplified version of what I did to automate swapping configuration files:

  1. I created two copies of my config.json file and put them at the root of my Titanium project: config.installr.json and config.production.json.
  2. I added the grunt-contrib-copy plugin with npm install grunt-contrib-copy --save-dev
  3. I updated my grunt script:

/* Define the pre- and post-build copy commands */
copy: {
  pre: {
    src: 'config.installr.json',
    dest: 'app/config.json',
  },
  post: {
    src: 'config.production.json',
    dest: 'app/config.json',
  },
},

/* after all the tasks are defined, load the plugin */
grunt.loadNpmTasks('grunt-contrib-copy');

/* finally, kick off all the tasks */
grunt.registerTask('default', ['copy:pre', 'tiapp', 'titanium', 'copy:post', 'shell']);

Now, when I run grunt, the first thing it does is copy my Installr-version of config.json to the app directory. It does the rest of its build tasks, then copies our day-to-day config.json file back. (That file has our testing endpoints listed in the env:development and env:test keys, and production endpoints in the env:production key. With it, we’re ready for manual dev or production builds.)

See the the Gist for full steps to set up Grunt, as well as our complete Grunt script. In reality, we’re not actually uploading via the Grunt script any more. We ran into occasional problems with the uploader failing, plus problems with parsing the change log if it contained quotes and apostrophes. We now to the clean, version bump, build tasks via Grunt then upload and send the emails out manually via the Installr Dashboard.

Don’t create variables you don’t need

We were having a problem in our app where occasionally photos would load only partway. You’d see the top portion of the photo and the rest would be white.

In testing, I was not seeing network errors. Nor was memory being exhausted. Listening in on the ImageView’s error event showed no errors being reported. It wasn’t always the same image that would fail and when one failed to load, later images on the page would fully load. Finally, the images would usually load if you refreshed the screen.

With these clues in hand, I was fairly confident that the image files weren’t bad, memory wasn’t the issue, and the network probably wasn’t the source of the problem either.

Here’s the Titanium the code I inherited:

for (var i = 0; i < $.product.photos.length; i++) {
  var photo = $.product.photos[i];
  var imgView = Ti.UI.createImageView({
  image: photo
  });
  // $.product_photos is a Ti.UI.ScrollableView
  $.product_photos.addView(imgView);
}

My supposition is that the loop was running faster than the ImageView creation. Before the image could be fully rendered the loop would overwrite the photo and imgView variables.

Based on that hunch, I refactored the code to:

for (var i = 0; i < $.product.photos.length; i++) {
  $.product_photos.addView(Ti.UI.createImageView({
  image: $.product.photos[i]
  }));
}

(Yes, I could/should have used a more functional approach… Move along.)

Since making this change, we haven’t seen the partial-image problem. It could be coincidence, or eliminating unnecessary variables might have resolved our issue. Only more testing will tell for sure.

Regardless, there’s no need to create variables as our code originally did. It can cause garbage collection issues and contribute to over-consumption of memory. And could lead to oddball problems like the ones we were seeing.

Update a ‘Waiting for Review’ app and keep the version number

Recently, I submitted an app update to Apple only to find a bug that really needed to be fixed before it got to customers. However, I wanted my new fix to use the same version number I had already started with. Since Apple had not yet reviewed my update, I was able to follow the steps below to update the binary, and keep the same version number.

  1. Using Safari, log onto iTunes Connect (rumors say other browsers won’t work, though I didn’t try).
  2. Go to My Apps and select the app and version that is waiting for review.
  3. At the top, next to the blue “i” info icon, click the link to “remove this version from review.”
    Remove build link
  4. Delete the binary: scroll down to find the build and hover over it; a minus button appears. Click it. Despite what it implies, this doesn’t actually delete the build (or build version number) from iTunes.
  5. Make sure to click Save.
  6. This is the critical step: you can’t use the exact same build/version number, but you can keep the major portion of it. You must update the app’s version number … my previous version was going to be 1.0.3, which I wanted to keep. So, I changed it to 1.0.3.1.
  7. Build the app for production and upload to the store using Xcode as usual.
  8. Back in iTunes Connect, after a brief wait for it to show up, select the new build and again click Save.
  9. Then click Submit for Review and finish the remainder of the submissions steps.

CommonJS modules instead of globals

When you need a value or method to be available in various places in your app, the temptation to use a global variable can be hard to resist. You should resist! Besides, there is a good, and easy, alternative.

CommonJS modules are a standardized way to create an easily reusable object. These modules can provide factory (creator) functions or provide libraries of related functions. In this post, I will concentrate on their use as an alternative to global variables.

Quickly, the basics are:

  • CommonJS modules are JavaScript files, typically placed in the app/lib folder, that follow a simple set of rules.
  • They offer a separate namespace, so chances of variable naming collisions are reduced.
  • You use them in your app with a simple var foo = require('bar'); syntax.
  • Specifically in the Titanium environment, modules are cached. This mean that there’s minimal overhead in re-requiring them throughout your app.

See Appcelerator’s docs, or the many other references you’ll find in a quick Google search for further info about creating and using CommonJS modules.

Here’s a simple CommonJS module that provides persistent storage across instances:

var counter = 0; // our persistent value
exports.getCounter = function() {
 return counter;
};
exports.increment = function() {
 counter++;
}
exports.reset = function() {
 counter = 0;
}

Now, let’s use it. In one of your controllers, you’d add:

var count = require('counter');
console.log(count.getCounter()); // 0
count.increment();
console.log(count.getCounter()); // 1

No big deal there. But check this out. Let’s say you have the following in a different controller:

var cnt = require('counter');
// if the first controller has run, the value 
// of the following will be 1 not 0
console.log(cnt.getCounter());

There you go, no global variables and value preservation across controllers. I’ve used this technique to:

  • Create a singleton timer with methods to add functions to a stack that get run periodically. (The stack is the preserved value in this example.)
  • Store common style values in non-Alloy apps.
  • Create a messaging bus that can be used in place of app-level events for passing messages and data throughout the app.