Saturday, June 30, 2012

Why I dropped PhoneGap Build

PhoneGap Build should have been awesome, and for a while, it was.

And then I needed to keep the screen on in my app. The fix is simple - you only need to add a single line to App.java: getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

The catch? There's no way to do this with PhoneGap Build. If you need any plugin or native code change, you're SOL, and sadly this isn't the end of the story.

PhoneGap updates quite frequently, and PhoneGap Build always lags in supporting the latest PhoneGap versions. If you're building your own binaries outside of the cloud, you can update PhoneGap whenever the hell you want. Need a same-day fix for a PhoneGap problem? You can do it. With PhoneGap Build, you're completely stuck at the whims of the developers. The problem isn't them, of course, it's the process. If anyone needs to do *anything* to enable me to use the latest PhoneGap version, there will *always* be a lag.

After re-implementing automated builds locally, I can build and install my production-minified application into the android emulator in about 1min 17sec. Before, it would take about 2 minutes *just* to build an APK in the cloud, let alone download it and install it into the emulator. Was it hard to drop PhoneGap Build? Not really. I've only taken on native Android releases so far, but the process was a breeze. The following gradle tasks let me
  • Build a debug APK
  • Build and sign a release APK
  • Install a debug build into the emulator
I'll probably pull all this into a plugin for Android build specific functionality when I work on creating iOS build tasks, but given how simple all this is, it really makes you wonder. Why would anyone use PhoneGap Build, and worse yet, why would you pay for it?

final List ANDROID_DRAWABLE_LOCATIONS = ['native/android/res/drawable', 'native/android/res/drawable-hdpi', 'native/android/res/drawable-ldpi'
        , 'native/android/res/drawable-mdpi', 'native/android/res/drawable-xhdpi']

task setupAndroidAppIcons() << {
    for (String location : ANDROID_DRAWABLE_LOCATIONS) {
        copy {
            from 'distribution/icons'
            include 'icon-114x114.png'
            into location
            rename { filename ->
                'icon.png'
            }
        }
    }
}

task setupAndroidSplashScreens() << {
    for (String location : ANDROID_DRAWABLE_LOCATIONS) {
        copy {
            from 'distribution/splashes'
            include 'splash480x800.png'
            into location
            rename { filename ->
                'splash.png'
            }
        }
    }
}

task setupAndroidAppFiles() << {
    copy {
        from 'build'
        exclude '.git'
        into 'native/android/assets/www'
    }
}

task setupAndroidPhonegapFiles() << {
    copy {
        from 'native/android'
        include 'cordova-*.js'
        into 'native/android/assets/www'
        rename { filename ->
            "phonegap.js"
        }
    }
}

task setupAndroidSoundFiles() << {
    copy {
        from './sounds'
        into './native/android/assets/sounds'
        include '*.*'
    }
}

task cleanAndroidAssets() << {
    delete('./native/android/assets')
    ANDROID_DRAWABLE_LOCATIONS.each {
        delete(it)
    }
}

task setupAndroidAssets(dependsOn: [setupAndroidAppIcons, setupAndroidSplashScreens, setupAndroidAppFiles, setupAndroidPhonegapFiles, setupAndroidSoundFiles]) << {
}

task buildNativeAndroidDebug(dependsOn: [cleanAndroidAssets, setupAndroidAssets]) << {
    exec {
        commandLine = ['ant', 'clean', 'debug']
        workingDir = new File('./native/android')
    }
}

task buildNativeAndroidRelease(dependsOn: [cleanAndroidAssets, setupAndroidAssets]) << {
    exec {
        commandLine = ['ant', 'clean', 'release']
        workingDir = new File('./native/android')
    }
}

task installNativeAndroidDebug(dependsOn: [buildNativeAndroidDebug]) << {
    exec {
        commandLine = ['adb', 'install', '-r', './native/android/bin/Wendler5_3_1-debug.apk']
    }
}