So you want to build Android apps… without Java? Nick Plante @zapnap brief introduction to Mirah and Pindah)
May 10, 2015
So you want to build Android apps… without Java?
Nick Plante @zapnap
(A brief introduction to Mirah and Pindah)
Who?
Nick Plante (not a Java developer)
Zerosum Labs Rails Rumble Rubydoc.info
Chinaccelerator
Contact me! @zapnap on Twitter http://github.com/zapnap
Why?
Because you’re a Ruby developer (familiarity).
Because Android is awesome. Because simplicity is elegance.
“Think Different.”
Java vs Ruby
Dynamic vs Static Simplicity vs Complexity & Verbosity Less Ceremony
Java is a systems programming language, after all
But… The JVM is an awesome platform Android’s use of the JVM (Dalvik) gives
us options
Java Alternatives
Android Scripting Environment (SL4A) Great for scripting; not great for applications Limited access to Android API / GUI Performance issues
Other JVM-based languages: Scala Clojure JRuby Mirah Groovy?
JRuby & Mr Ruboto
Ruboto is your friend! JRuby interface / framework for Android
APIs http://ruboto.org
But JRuby runtime overhead is a problem Slow startup (~10 seconds) Large APK size▪ HelloWorld: 3.4MB compressed, 10MB installed
Introducing Mirah
Mirah compiles straight to Java bytecode Very fast, no extra overhead
Syntax is very Ruby-ish Statically-typed with local type inference “Ruby with type annotations”
No runtime library Mix and match Java code
Warning!
Mirah is still a very young language (v0.0.7)
Tooling is very, very alpha Advantage: Eclipse (Java) Redcar looks promising
Compilation errors are very, very not-so-funNativeException: jmeta.SyntaxError: expected Ensure before ' { |a b| Inte' (at line: 16, char: 40)
One thing at a time
We’ll get to Android in just a second First let’s see some basic Mirah
syntax…
# fib.mirahdef fib(a:int):int if a < 2 a else fib(a-1) + fib(a-2) endend
puts fib(20)
Ruby vs Mirah
# fib.rubydef fib(a) if a < 2 a else fib(a-1) + fib(a-2) endend
puts fib(20)
Parameter type declaration???SRSLY?
Return type can often be inferred.
.class output
Produces a java .class $ mirahc fib.mirah public static int fib(int)
Can also produce .java code $ mirahc -j fib.mirah * I have no idea why you would want to
do this.
.to_java => “ick”
// Generated from hello.mirahpublic class Hello extends java.lang.Object { public static void main(java.lang.String[] argv) { java.io.PrintStream temp$1 = java.lang.System.out; temp$1.println(Hello.fib(20)); } public static int fib(int a) { return (a < 2) ? (a) : ((Hello.fib((a - 1)) + Hello.fib((a - 2)))); }}
Challenges / Major Differences Java stdlib: both a blessing and a curse
List, HashMap, etc▪ arr.get(1) vs arr[0]
Blocks work, but syntactic sugar required For example, List#each can be used (array) But HashMap#each does not exist
No optional arguments, no *splats, no ranges
Using Java’s Standard Libraryimport java.util.Collectionsimport java.util.HashMapimport java.util.List
class ListPrinter def print(list:List) puts "first item: #{list.get(0)}" list.each do |item| puts "item: #{item}" end end end
class HashPrinter def print(map:HashMap) map.keySet.each do |key| puts "#{key}: #{map.get(key)}" end end end
map = { 'batman' => 'bruce wayne', 'superman' => 'clark kent' }HashPrinter.new.print(map)
list = ['peter', 'stewie', 'brian']ListPrinter.new.print(list)
Get Mirah
# Install JRuby 1.6 if you haven’t already# (or rvm use jruby)
$ jruby –S gem install mirah
$ mirah -e "puts 'hello world'”> hello world
Now What?
Get the Android SDK
Download and install it: http://developer.android.com/sdk/index.html
Notes on building from the command line: http://developer.android.com/guide/developing/
projects/projects-cmdline.html
Set up your environment (see above): Make sure to set JAVA_HOME, CLASSPATH, and
put your platform-tools directory in your path
Android SDK / Virtual Devices
The anatomy of a typical Android application Activities Intents Manifest File XML Layouts (Views) Services Content Providers
(Lots to learn)
Hello Pindah
Garrett, Protoform, Mirahndroid => Pindah
Goals Make it easy to get started with Android + Mirah Make day to day development tasks easier Provide project structure and conventions
Application skeleton generator Rake tasks to hide Ant nastiness
Because XML is pain Use Rake to compile / debug / install / etc
What Pindah Does NOT Do
No “pretty” wrappers for Android APIs You must learn the APIs to work
effectively
Does not provide alternatives to XML-based Manifest or view layouts https://github.com/objo/droid-views
Get Pindah
# For more information, see# http://github.com/mirah/pindah$ jruby –S gem install pindah
# Generate an Android application skeleton$ pindah create org.example.hello
[/path/to/hello_world] [HelloActivity]
Android App Skeleton
├── AndroidManifest.xml├── Rakefile├── libs├── res│ ├── drawable-hdpi│ │ └── ic_launcher.png│ ├── drawable-ldpi│ │ └── ic_launcher.png│ ├── drawable-mdpi│ │ └── ic_launcher.png│ ├── layout│ │ └── main.xml│ └── values│ └── strings.xml└── src └── org └── example └── hello └── HelloActivity.mirah
Managed for you by Pindah:
• default.properties• build.properties• local.properties• build.xml
Pindah Rake Tasks
$ rake -TAndroid SDK Tools Revision 8Project Target: Android 2.1-update1API level: 7
------------------Resolving library dependencies:No library dependencies.
------------------
Importing rules file: tools/ant/main_rules.xmlrake clean # Removes output files created by other targets.rake compile # Compiles project's .mirah files into .class filesrake debug # Builds the application and signs it with a debug key.rake install # Installs/reinstalls the debug package onto a running ...rake javac # Compiles R.java and other gen/ files.rake logcat # Tail logs from a device or a device or emulatorrake release # Builds the application.rake spec # Print the project specrake uninstall # Uninstalls the application from a running emulator or dev...
Android Activity Boilerplate
# HelloActivity.mirahpackage org.example.hello
import android.app.Activity
class HelloActivity < Activity def onCreate(state) super state setContentView R.layout.main endend
# HelloActivity.javapackage org.example.hello;
import android.app.Activity;
public class HelloActivity extends Activity{ /** Called when the activity is first
created. */ @Override public void onCreate( Bundle savedInstanceState) { super.onCreate( savedInstanceState); setContentView( R.layout.main); }}
Running the Example App$ cd hello_world$ rake install=> WIN.
Well, that was boring
Slightly More Interesting
More expressive code == visible improvement
Example application “Up or Down?” website testing app http://github.com/zapnap/upordown
Questions: What do Android callbacks look like? How do I leverage 3rd party (Java) libraries? How do I handle exceptions? How does Mirah deal with scope? (it’s weird)
Up or Down? Down or Up?
Android Listeners (Java)
// Sample button click listener in Java
// Create an anonymous implementation of OnClickListenerprivate OnClickListener mClickListener = new OnClickListener() { public void onClick(View v) { // handle click event }};
protected void onCreate(Bundle savedValues) { ... // Capture our button from layout Button mSubmit = (Button)findViewById(R.id.submit_btn); // Register the onClick listener with the impl above mSubmit.setOnClickListener(mClickListener); ...}
StatusActivity + Listenersclass StatusActivity < Activity def onCreate(state) super state setContentView R.layout.main @url = EditText findViewById(R.id.url_txt) @submit = Button findViewById(R.id.submit_btn)
setListeners end
def setListeners this = self # SHORTCUT: click listener must implement onClick @submit.setOnClickListener do |v| status = this.checkSiteStatus(this.getUrl) this.showResult status end end
Scoping for ivars and self is incomplete. Assign a local var within scope.
Easy to add anonymous listeners with this syntax (implements a single method interface)
Using External Java LibrariesPut jars in libs folder and import to use. Simple! (must import Android libs too)
import java.net.URLimport java.net.SocketTimeoutException
import org.jsoup.Jsoupimport org.jsoup.nodes.Document
def checkSiteStatus(address:String):String return "Please specify a URL to test" if address.equals('')
begin doc = Jsoup.connect( "http://downforeveryoneorjustme.com/" + address ).get res = doc.select("#container").first.text
Log.d 'StatusActivity', 'Full response from server is: ' + res res.substring(0, res.indexOf('Check another')) rescue SocketTimeoutException => ex "Unable to contact the server. How ironic!” endend
Exception handling works like it does in Ruby. Must import specific exceptions.
Wrapping Up: Dialog Example
def getUrl @url.getText.toStringend
def showResult(message:String) alert = AlertDialog.Builder.new(self) alert.setTitle 'Site Test Results’ alert.setMessage message alert.setPositiveButton('OK') do |dialog, w| dialog.dismiss end
alert.showend
Android XML Layouts (main.xml)
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <LinearLayout android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="wrap_content"> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:textSize="20sp" android:textStyle="bold" android:text="@string/app_title" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:textSize="18sp" android:textStyle="bold" android:layout_marginBottom="10dip" android:text="@string/app_subtitle" /> <EditText android:id="@+id/url_txt" android:layout_width="fill_parent" android:singleLine = "true" android:layout_height="wrap_content" android:inputType="textUri" android:hint="@string/url_example" /> <Button android:id="@+id/submit_btn" android:layout_width="140dip" android:layout_marginTop="6dip" android:layout_height="wrap_content" android:layout_gravity="center" android:gravity="center" android:text="@string/submit_btn" /> </LinearLayout> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:layout_alignParentBottom="true" android:autoLink="web" android:text="@string/powered_by" /></RelativeLayout>
<EditText android:id="@+id/url_txt" android:layout_width="fill_parent" android:singleLine = "true" android:layout_height="wrap_content" android:inputType="textUri" android:hint="@string/url_example" />
more info
app title[ url input ][ button]
One More Thing
Android Manifest lists top-level activities and required permissions
Our app requires Internet access Add the permission to
AndroidManifest.xml: <uses-permission android:name="android.permission.INTERNET" />
It Works!
Ideas for Next Steps
Implement a ProgressDialog And perform the site check in an AsyncTask
Record a log of website test history to a ListView Allow users to browse test history through a
separate Activity Store test history to a local Sqlite database
(and ListAdapter)
Fork me at http://github.com/zapnap/upordown
Conclusions (or lack thereof) Mirah is a nice midpoint between Ruby and Java
Well-suited for Dalvik JVM work But still very immature / not yet practical for daily use Opportunity to help push mobile dev forward
Lack of good IDE support Makes working with the (massive) Android API difficult
Debugging is a pain in the butt ADB (Rake logcat) is incredibly useful; learn to use it
I personally still prefer mobile web development ;-) but sometimes native is the way to go!
And so it goes
Mirah Language Resources http://mirah.org http://github.com/mirah/mirah
Pindah and Mirah and Android Oh My! http://github.com/mirah/pindah http://github.com/technomancy/garrett (experiments) http://threebrothers.org/brendan (urbanspoon)
General Android Development http://developer.android.com/sdk/index.html http
://developer.android.com/guide/developing/projects/projects-cmdline.html