Android Studio Integration

Editing Gradle Scripts

Plugin Module

  1. From the Project sidebar in Android Studio, open LuaLoader.java. You will see errors about missing libraries:
  1. From within the Gradle Scripts tree, open build.gradle (Module: plugin)
  1. Between the android and dependencies blocks, add a variable defining where the CoronaEnterprise directory is. This variable points to the symbolic link, which points to the real CoronaEnterprise directory.
def coronaEnterpriseDir = "$rootDir/CoronaEnterprise"
  1. Replace everything inside the dependencies block with the following lines. These tell Android Studio to look for extra libraries inside Corona Enterprise and inside the libs directory of the plugin module.
compile fileTree(dir: "$coronaEnterpriseDir/Corona/android/lib/Corona/libs", include: '*.jar')
compile fileTree(dir: 'libs', include: '*.jar')
  1. Remove the entire buildTypes block.

  2. To finish the plugin section of build.gradle, you need to add an extra task to create a plugin.library.jar that can later be submitted to the Corona Marketplace or included in another project. Add this code before the dependencies block:

task exportPluginJar (type: Copy, description: 'place the plugin JAR file in the outputs directory and give it a name from the AndroidManifest.xml') {
    def pluginName = new XmlSlurper().parse(file('src/main/AndroidManifest.xml')).@package.text()
    from "$buildDir/intermediates/bundles/release/"
    into "$buildDir/outputs/jar"
    include 'classes.jar'
    rename 'classes.jar', "${pluginName}.jar"

    doFirst {
        println '== exportPluginJar =='
    }
}

tasks.withType(JavaCompile) {
    compileTask -> compileTask.dependsOn('exportPluginJar')
}
Note

The defined task gets the name of the plugin from the AndroidManifest.xml file inside the plugin module and it creates the .jar file (plugin.library.jar) inside the plugin/build/output/jar/ directory after the project has been built.

  1. Click the Sync Now button in the upper-right corner. Your build.gradle for the plugin should now look like this (and LuaLoader.java should not have import errors):
  1. At this point, you can close this build.gradle and proceed to the next section. You can also fix the JavaDocs and remove unused imports so Android Studio will stop complaining about them in LuaLoader.java.
Notes
  • If your plugin has additional dependencies, they should be added inside the dependencies block of build.gradle (Module: plugin). You can also put stand-alone .jar libraries inside plugin/libs/ or app/libs/.

  • If your Enterprise project does not use a separate plugin module, be sure that the Corona Enterprise libraries are compiled into you main app module by adding this line to the dependencies block of your main app module:

    compile fileTree(dir: "$coronaEnterpriseDir/Corona/android/lib/Corona/libs", include: '*.jar')

  • In build.gradle files, you must always check what versions of Android tools, Android APIs, and other libraries are used. Android SDK evolves and these version numbers will increase. Note that some plugins may require older versions of specific libraries as they might be incompatible with new ones. Thus, you should always take proper care of versions.

App Module

  1. From within the Gradle Scripts tree, open build.gradle (Module: app)
  1. Remove the entire buildTypes block.

  2. Now you must modify this script to make a healthy .apk on build. In the dependencies block, remove the junit line and add the following line. This tells Android Studio to compile the plugin module first and include it into the .apk.

compile project(':plugin')
Note

If you have a plugin module, compile it into your app with compile project(':plugin'). Otherwise, ensure that Corona's Java binaries are included in your app module via the following (we'll define $coronaEnterpriseAndroidLibDir in the next step).

compile fileTree(dir: "$coronaEnterpriseAndroidLibDir/libs", include: '*.jar')

  1. Now add the following variable definitions before the android block. The first two extract the application name and .apk filenames from the AndroidManifest.xml file, while the last six define the locations of various project and Corona Enterprise directories needed to build the project correctly.
// Application name variables
def appName = new XmlSlurper().parse(file('src/main/AndroidManifest.xml')).@package.text()
def apkName = appName.toString().split('\\.').last()

// Paths used throughout the build process
def coronaEnterpriseDir = "$rootDir/CoronaEnterprise"
def assetsDir = "$projectDir/src/main/assets"
def jniLibsDir = "$projectDir/src/main/jniLibs"
def coronaEnterpriseMacBinDir = "$coronaEnterpriseDir/Corona/mac/bin"
def coronaEnterpriseSharedDir = "$coronaEnterpriseDir/Corona/shared"
def coronaEnterpriseAndroidLibDir = "$coronaEnterpriseDir/Corona/android/lib/Corona"
  1. Now, before the dependencies block, we begin adding all of the crucial tasks for building the .apk. First, add the cleanAssets task below.
task cleanAssets(type: Delete, description: 'remove Corona assets and libcorona.so') {
    delete "$assetsDir"
    delete "$jniLibsDir"

    doFirst {
        println "== cleanAssets =="
    }

    doLast {
        new File("$assetsDir").mkdirs()
    }
}
Note

The cleanAssets task cleans up the files that are created during execution of the tasks which follow. This needs to be executed every time so that updates to your lua project can be picked up and resigning of the app can occur.

  1. With the ability to clean the Corona-specific portions of the project, now add the compileLua task below.
task compileLua(type: Exec, description: 'compile Lua source code') {
    executable = "$coronaEnterpriseMacBinDir/lua"
    workingDir = "$coronaEnterpriseMacBinDir"
    args = [
            '-e',
            "package.path='$coronaEnterpriseSharedDir/bin/?.lua;$coronaEnterpriseSharedDir/bin/?/init.lua;'..package.path",
            "$coronaEnterpriseSharedDir/bin/Compile.lua",
            'mac',
            "$coronaEnterpriseDir"
    ]

    environment = [
            LUA_CPATH                   : "$coronaEnterpriseMacBinDir/?.so",
            TARGET_PLATFORM             : 'android',
            PROJECT_DIR                 : "$rootDir",
            CORONA_COPY_PNG_PRESERVE    : '--preserve',
            CONFIGURATION               : 'release',
            CORONA_ASSETS_DIR           : "[RELATIVE_PATH_TO_CORONA_PROJECT]",  // Default is: "$rootDir/../Corona"
            CORONA_TARGET_RESOURCES_DIR : "$assetsDir",
            CORONA_TARGET_EXECUTABLE_DIR: "$assetsDir"
    ]

    dependsOn 'cleanAssets'

    doFirst {
        println '== compileLua =='
    }
}
Note

The compileLua task compiles your Lua source code from the Corona directory into bytecode stored inside the resources.car file. It also places all other assets from that directory into the app/src/main/assets/ directory so they get bundled into the .apk.

  1. Now that the script can compile your Lua code, add the two copy tasks below.
task copyCoronaResources(type: Copy, description: 'include resources from Corona Enterprise') {
    from fileTree(dir: "$coronaEnterpriseAndroidLibDir/res", include: '**/*')
    into "$projectDir/src/main/res"

    dependsOn 'compileLua'

    doFirst {
        println '== copyCoronaResources =='
    }
}

task copyCoronaNativeLibs(type: Copy, description: 'include precompiled libraries from Corona Enterprise') {
    from fileTree(dir: "$coronaEnterpriseAndroidLibDir/libs", include: '**/*.so')
    into "$jniLibsDir"

    dependsOn 'copyCoronaResources'

    doFirst {
        println '== copyCoronaNativeLibs =='
    }
}
Notes
  • The copyCoronaResources task takes Android resource files from Corona Enterprise (widgets and image sheets for example) and copies them to the app/src/main/res/ directory so they also end up inside the .apk.

  • The copyCoronaNativeLibs task takes .so libraries from Corona Enterprise and copies them into the project's app/src/main/jniLibs directory. Unlike Ant build where .jar and .so files would sit in the same root libs directory, Android Studio expects .so files in the app/src/main/jniLibs directory for inclusion inside the .apk.

  1. Since all the Corona assets and libraries are in place, now we add a task to sign the build defined below.
task certifyBuild(type: Exec, description: 'certify libcorona.so with resource.car hash and developerkey.cert') {
    executable = "$coronaEnterpriseMacBinDir/CoronaBuilder.app/Contents/MacOS/CoronaBuilder"
    workingDir = "$coronaEnterpriseMacBinDir"
    args = [
            'app_sign',
            'sign',
            "$coronaEnterpriseSharedDir/resource/developerkey.cert",
            "$assetsDir/resource.car",
            "$jniLibsDir/armeabi-v7a/libcorona.so",
            'little'
    ]

    dependsOn 'copyCoronaNativeLibs'

    doFirst {
        println '== certifyBuild =='
    }
}
Note

The certifyBuild task digitally signs libcorona.so (inside app/src/main/jniLibs) with a fingerprint generated from the resources.car file. Without this task, the Corona application will fail to start on the device and yield a message about not being able to find/load main.lua.

  1. To kick off all the tasks we've added, we need to make them all dependent on the standard Android build process. This is done by adding a dependsOn to the standard pre-build tasks shown below.
tasks.preBuild.dependsOn('certifyBuild')
Note

You will notice that all tasks are executed in order defined by a dependsOn directive. You also tell Android Studio that its first task (preBuild) should also depend on them with this line:

tasks.preBuild.dependsOn('certifyBuild')

If you update Corona Enterprise, these tasks will pick up the most recent files the next time you build the project, making the update process painless.

  1. For release builds, place your keystore file into the [PATH_TO_PROJECT_TEMPLATE]/App/android/ directory and add the following signingConfigs block inside the android block, directly before the defaultConfig block. If you need assistance on creating a keystore, please see the Signing and Building — Android guide.
signingConfigs {
    release {
        storeFile file("$rootDir/apptemplate.keystore")
        storePassword 'apptemplate'
        keyAlias 'apptemplate'
        keyPassword 'apptemplate'
    }
}
  1. Within the android block, replace the applicationId value in the defaultConfig block with "$appName". Additionally, add this line at the bottom of the defaultConfig block:
signingConfig signingConfigs.release
  1. Finally, for the .apk file to be properly named, add this code at the end of the android block, directly following the defaultConfig block:
applicationVariants.all { variant ->
    variant.outputs.each { output ->
        output.outputFile = file(output.outputFile.getPath().replace('/app-', "/${apkName}-"))
    }
}
  1. Click the Sync Now button in the upper-right corner. Once this finishes and there are no errors to resolve, you can close this build.gradle.