RubyMotion Project Management Guide for Android

In this guide we will explain how to create new RubyMotion Android projects, then configure and maintain them.

1. Creation

To create a new RubyMotion project, pass the create command to /usr/bin/motion with the android template. It will create a new project directory.

$ motion create --template=android Hello
$ cd Hello
$ ls
Rakefile	assets		resources	vendor
app		spec

1.1. Project Templates

Important
By default RubyMotion creates iOS projects. Setting up the android template is important here, otherwise you will get an iOS project directory.

Developers can add 3rd-party templates in the ~/Library/RubyMotion/template directory. A RubyMotion project template is a directory that includes a files sub-directory which will contains the files that will be created by motion create.

You can refer to the builtin templates in /Library/RubyMotion/lib/motion/project/template for documentation.

1.2. Project Anatomy

The following table illustrates the anatomy of a project directory.

File/Directory Purpose

Rakefile

This file contains the configuration of the project, as well as a default set of tasks. It can be edited to change the configuration or add new tasks.

.gitignore

This file contains file patterns that should be ignored by the source control management software (for instance, build files). This file is used by Git, but it can be ported to other SCMs.

app/

This directory contains the Ruby code of the project. In a new project, a main_activity.rb file is created automatically.

resources/

This directory contains the resources files of the project. In a new project, this directory is empty.

assets/

This directory contains the assets files of the project. In a new project, this directory is empty.

spec/

This directory contains the specification files of the application. In a new project, a default specification file is created automatically.

RubyMotion projects are based on Rake. Essential rake tasks will be covered in the following sections. To see the full list of available tasks:

$ rake -T
Tip
Rake is the de-facto build system framework for Ruby. It is similar to make and ships by default in OS X.

2. Configuration

The rake config task will dump the project configuration.

$ rake config

Each configuration variable has a sensible default value that can be manually overriden in the Rakefile file.

2.1. Options

Variable Discussion

name

Project name, as a String. The default value is the name passed to motion create.

package

Project package name, as a String. The default value is com.yourcompany. plus the name of the project, in lower case.

icon

Project icon name, as a String. The value must be the basename (without the extension) of an icon file that exists inside the drawable resource directory. The default value is nil.

version_code

Version code of the application (maps to the versionCode Android manifest variable), as a String. The default value is "1".

version_name

Version name of the application (maps to the versionName Android manifest variable), as a String. The default value is "1.0".

main_activity

Main activity class name, as a String. The default value is "MainActivity".

sub_activities

Subactivities class names, as an Array of String. The default value is an empty array.

application_class

Application class name, as a String. Set this variable if you want to provide a custom Application class for the project. The default value is nil which means the project will use the generic Application class.

api_version

The minimum version of the Android API to target (maps to the minSdkVersion Android manifest variable), as a String. The default value is the higher stable version available in the currently-installed SDK.

target_api_version

The target version of the Android API to target (maps to the targetSdkVersion Android manifest variable), as a String. The default value is the higher stable version available in the currently-installed SDK. We recommend that you don’t change this variable and set api_version instead.

arch

The CPU architecture to build the application for, as a String. The default value is "armv5te" and it must be an ARM-based architecture.

permissions

List of application permissions, as an Array. Permissions can be represented either in a full name pattern using a String (ex. "android.permission.INTERNET") or a shortcut version as a Symbol (ex. :internet). The default is an empty Array.

features

List of application features (maps to the <uses-feature> Android manifest element), as an Array of String. The default is an empty Array.

services

List of application services (maps to the <service> Android manifest element), as an Array of String. The default is an empty Array.

files

Project files, as an Array. The default value is an array containing all .rb file in the app directory.

build_dir

Path to the directory for build products, as a String. It must be relative to the project directory. The directory will be created by the build system if it does not exist yet. If it cannot be created, a temporary directory will be used instead. The default value is "build".

resources_dirs

Directories for resources files, as an Array of String. The default value is ["resources"].

assets_dirs

Directories for assets files, as an Array of String. The default value is ["assets"].

specs_dir

Directories for specification files, an Array of String. It must be relative to the project directory. The default value is "spec".

sdk_path

Path to the Android SDK directory. The default value is the value of the RUBYMOTION_ANDROID_SDK environment variable.

ndk_path

Path to the Android NDK directory. The default value is the value of the RUBYMOTION_ANDROID_NDK environment variable.

logs_components

List of components to log to the terminal when running an app in development, as an Array of String. The default value includes core Android components (such as the runtime) as well as the application component.

2.2. Providing Custom Values

Custom values for the configuration settings can be added by tweaking the Motion::App.setup block in the Rakefile file.

As an example, let’s take the configuration block of a fictional app that has a custom icon file and needs permission to connect to the internet.

Motion::Project::App.setup do |app|
  app.name = 'My App'
  app.icon = 'icon'
  app.permissions << :internet
end

Additionally, you can provide arbitrary values that will be added to the application manifest via the app.manifest property.

The child method returns the first element of the given type in the current element. You can also pass a block which will be called with the element.

Properties of the element can be set via the []= method, or you can set multiple properties via the update method.

Child nodes can be added with the add_child method, which also accepts a block.

Finally, the children method returns an array with all the child elements of a given type.

Motion::Project::App.setup do |app|
  app.manifest.child('application') do |application|
    application['android:theme'] = '@style/AppTheme'

    application.update('android:description' => 'My awesome app',
                       'android:isGame'      => true)

    application.add_child('uses-library').update('android:name' => 'com.google.android.maps')

    application.children("uses-library")|
  end
end

For a complete explanation of the valid elements and properties of the manifest file, please check the official Android Manifest guide.

2.3. Files Dependencies

By default, RubyMotion will compile files in the regular sorting order of the filesystem. When a RubyMotion application starts, the main scope of each file will then be executed in that specific order.

Sometimes, you will want to customize the order, if for instance one file makes use of a constant defined in another.

$ cat app/foo.rb
class Foo
end
$ cat app/bar.rb
class Bar < Foo
end

In the example above, using the default order, bar.rb will be compiled before foo.rb resulting in a constant lookup error, because the Foo constant has not been defined yet when we execute the code in bar.rb.

To fix that problem, the files_dependencies method can be used in the Rakefile. This method accepts a Hash which should be a set of files dependencies.

Motion::Project::App.setup do |app|
  # ...
  app.files_dependencies 'app/bar.rb' => 'app/foo.rb'
end

After this change, the build system will compile foo.rb before bar.rb.

2.4. Vendoring 3rd-Party Libraries

The Android SDK has a significant amount of functionality built-in that you can use in your RubyMotion project. However, sometimes you will have to use a 3rd-party library that provides a feature missing from the system.

RubyMotion lets you integrate 3rd-party Java libraries. It is recommended to keep these libraries required by the project in the same place, for instance under a vendor directory.

The vendor_project method can be called from the Rakefile. It accepts a set of key/value arguments.

Jar Files

To vendor a 3rd-party jar, simply provide the :jar key with the full path of the library as its value.

As an example, to vendor the android-support-v4.jar library, do the following.

Motion::Project::App.setup do |app|
  # ...
  app.vendor_project :jar => "vendor/android-support-v4.jar"
end

Certain Java libraries, such as Google Play Services, come with resource files and a custom manifest file. These need to be provided to the RubyMotion build system, using the respective :resources and :manifest keys/values.

Motion::Project::App.setup do |app|
  # ...
  app.vendor_project :jar => "vendor/google-play-services_lib/libs/google-play-services.jar",
     :resources => "vendor/google-play-services_lib/res",
     :manifest => "vendor/google-play-services_lib/AndroidManifest.xml"
end

2.5. Extending Classes with Java

RubyMotion lets you extend a Ruby-defined class with Java code.

This is currently useful when you have to override a Java constructor. Due to technical limitations of the Java VM, RubyMotion cannot override constructors in Ruby, so it has to be done in pure Java.

As an example, let’s assume you have a custom subclass of the Android::App::IntentService class, in a app/my_service.rb file.

class MyService < Android::App::IntentService
  ...

This subclass does not provide a default constructor, and the Android system requires one in order to instantiate the service.

We can provide Java code that will implement that constructor, in a app/my_service.java file.

public MyService() {
  super("MyService");
}

The build system will detect this file and automatically merge the Java code into the class implementation.

Note
The name of the file is important here, and must correspond to the name of the class. The file name must be either the name of the class, or its underscore representation. For instance, both my_service.java and MyService.java will be recognized by the build system.

3. Build

RubyMotion will build development and release versions of the project into the temporary build directory. For each version there will be one package for the ARM architecture that will run on both the emulator and the device.

The following steps are performed during the build process:

  1. It compiles each Ruby source code file into optimized machine code, translating the Ruby syntax tree into an intermediate representation language (using LLVM), then assembly. The compiler will generate code for ARM instruction sets and ABIs depending on the target.

  2. It links the machine code with the RubyMotion runtime statically to form a shared library for JNI (Java Native Interface). The linker also includes metadata for the C APIs that the project uses, as well as 3rd-party libraries vendored from the configuration.

  3. It generates Android DEX bytecode for the project classes. Note that the bytecode only contains the class interfaces (such as name, subclass and method list), not the actual class and method code, which is compiled into machine code (in the JNI library).

  4. It creates an .apk archive and copies the files there. The AndroidManifest.xml file generated based on the project configuration. Each resource file in the resources and assets directories are copied in the bundle. 3rd-party libraries (.jar files) are also copied.

  5. It codesigns the bundle with the appropriate Java Android keystore.

3.1. Development

The rake build task builds a development version of the project.

$ rake build

Normally the user does not need to explicitly call this task, as it is a dependency of the rake emulator and rake device tasks, which are often used for development.

Inside the Rakefile, you can determine if the project is being built in development mode by using the app.development or app.development? methods.

Motion::Project::App.setup do |app|
  # ...
  if app.development?
    # apply custom options for development
  end

  app.development do
    # another variant
  end
end

The application will be code-signed using the debug Java Android keystore, located as ~/.android/debug.keystore. If the keystore file does not exist, RubyMotion will create it for you.

3.2. Release

Release builds are created using the rake release task.

$ rake release

This task can be used when you want to distribute versions of your app to beta-testers, and also upload them to the Google Play Store.

Inside the Rakefile, you can determine if the project is being built in release mode by using the app.release or app.release? methods.

Motion::Project::App.setup do |app|
  # ...
  if app.release?
    # apply custom options for release
  end

  app.release do
    # another variant
  end
end

The task requires a proper release keystore to be configured in the project, which can be done using the app.release_keystore_path method, which accepts two arguments: the full path to the keystore file, and the alias name of the key.

Motion::Project::App.setup do |app|
  # ...
  app.release_keystore(File.expand_path("~/my-release-key.keystore"),
      "release-key")
end

For more information on how to generate a release keystore file check out the App Signing document on the Android developer website.

3.3. Cleaning

The rake clean task empties the build directory and cleans the build directories of the vendored projects.

$ rake clean

4. Running

You can run apps either in the built-in Android emulator or an USB-connected Android device you configured for development.

4.1. Emulator

The Android SDK comes with an emulator that you can use to run apps in your computer.

Note
At the time of this writing, the emulator is too slow to be used efficiently in development. We highly recommend using a device instead.

The rake emulator task will build and run a version of the app in the emulator. The default rake task defaults to it.

$ rake
$ rake emulator

In order for the task to work, the emulator must be up and running.

First, you have to create an Android Virtual Device (AVD). To do so, follow these steps:

  1. Launch the android command-line tool from the terminal (which should be located as $RUBYMOTION_ANDROID_SDK/tools/android).

  2. Hit the Create button.

  3. In the create window, make sure the CPU/ABI is set to ARM (armeabi). This is important, as RubyMotion does not support other architectures for Android development.

  4. Provide a name and an appropriate API target for the device, then hit the OK button.

Once that the device has been created, you can start it from the main window by selecting it from the list, then click the Start button.

An emulator window will appear and the device will start. Please note that starting a virtual device can take some time. You must wait until you see the Android home screen.

4.2. Device

Running a RubyMotion app on an Android device is easy; just make sure it’s properly configured for development (check out the Getting Started guide for more information), then simply connect it and use the rake device task.

$ rake device

4.3. Interactive Console

Both rake emulator and rake release tasks will install and start the application, then an interactive console (read-eval-print-loop) will appear in the terminal.

You can type expressions in the terminal, and they will be evaluated right in the application process.

$ rake device
...
>> "hello".upcase
=> "HELLO"

Inside the console, the self variable points to the current Android activity for your convenience. By default, this will be the main activity object.

>> self
=> #<MainActivity:0x100502>