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 |
---|---|
|
Project name, as a |
|
Project package name, as a |
|
Project icon name, as a |
|
Version code of the application (maps to the |
|
Version name of the application (maps to the |
|
Main activity class name, as a |
|
Subactivities class names, as an |
|
Application class name, as a |
|
The minimum version of the Android API to target (maps to the |
|
The target version of the Android API to target (maps to the |
|
The CPU architecture to build the application for, as a |
|
List of application permissions, as an |
|
List of application features (maps to the |
|
List of application services (maps to the |
|
Project files, as an |
|
Path to the directory for build products, as a |
|
Directories for resources files, as an |
|
Directories for assets files, as an |
|
Directories for specification files, an |
|
Path to the Android SDK directory. The default value is the value of the |
|
Path to the Android NDK directory. The default value is the value of the |
|
List of components to log to the terminal when running an app in development, as an |
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:
-
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.
-
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.
-
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).
-
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.
-
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:
-
Launch the
android
command-line tool from the terminal (which should be located as$RUBYMOTION_ANDROID_SDK/tools/android
). -
Hit the Create button.
-
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.
-
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>