Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Groovy for Android

Groovy for Android

Groovy and SwissKnife for being more productive in your Android development

Guillaume Laforge

June 23, 2015
Tweet

More Decks by Guillaume Laforge

Other Decks in Technology

Transcript

  1. Groovy on Android Guillaume Laforge Project Ninja and Advocate —

    Restlet @glaforge I’ll put the slides online on speakerdeck.com/glaforge
  2. 3 APISpark — Backend as a Service • API platform

    for managing, documenting, and hosting APIs • Data stores • built-in entity store • Parse, Firebase & SQL wrappers • Google Sheets wrapper! • File stores • built-in file store • Github file store • S3 file store
  3. 3 APISpark — Backend as a Service • API platform

    for managing, documenting, and hosting APIs • Data stores • built-in entity store • Parse, Firebase & SQL wrappers • Google Sheets wrapper! • File stores • built-in file store • Github file store • S3 file store Well suited for your 
 Backend-as-a-Service needs!
  4. 4 RetroFit @Grab('com.squareup.retrofit:retrofit:1.9.0')
 import retrofit.*
 import retrofit.http.*
 
 interface StarWarsService

    {
 @Headers(["Accept: application/json" , "User-Agent: Firefox"])
 @Get('/people/{id}')
 People retrieve(@Path('id') String id)
 }
 
 class People { String name }
 
 def restAdapter = new RestAdapter.Builder()
 .setEndpoint('https://starwars.apispark.net/v1/').build()
 
 def service = restAdapter.create(StarWarsService)
 assert service.retrieve('1').name == 'Luke Skywalker'
  5. 9 New York Times — Getting Groovy with Android New

    York Times recruits a Groovy / Android expert http://bit.ly/nyt-job
  6. 10 What does NYT likes about Groovy on Android? •

    No Java 8, no lambda on Android… Func0 func = new Func0<string>() { @Override public String call() { return "my content"; } }; Async.start(func);
  7. 11 What does NYT likes about Groovy on Android? •

    No Java 8, no lambda on Android… Async.start { "my content" }
  8. 11 What does NYT likes about Groovy on Android? •

    No Java 8, no lambda on Android… Async.start { "my content" } Good bye annonymous inner classes!
  9. 12 What does NYT likes about Groovy on Android? •

    Groovy code more concise and more readable • but just as type-safe as needed!
 (with @TypeChecked) • but just as fast as needed!
 (with @CompileStatic)
  10. 15 Groovy — a multi-faceted language Alternative language 
 for

    the JVM An alternative language for the JVM
  11. 16 Groovy — a multi-faceted language Closely 
 resembles 


    Java Java code is also valid Groovy code!
  12. 17 Groovy — a multi-faceted language Multiple 
 programming 


    flavors Statically type checked and / or compiled
  13. 18 Groovy — a multi-faceted language Seamless Java integration
 &

    interoperability No bridge to cross between languages
  14. 27 Modify your app’s Gradle build apply plugin: 'com.android.application' android

    { compileSdkVersion 20 buildToolsVersion "20.0.0" defaultConfig { applicationId "com.appspot.glaforge.hellogroovyworld" minSdkVersion 15 targetSdkVersion 20 versionCode 1 versionName "1.0" } buildTypes { release { runProguard false proguardFiles getDefaultProguardFile('proguard-android.txt'), 
 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) }
  15. 27 Modify your app’s Gradle build apply plugin: 'com.android.application' android

    { compileSdkVersion 20 buildToolsVersion "20.0.0" defaultConfig { applicationId "com.appspot.glaforge.hellogroovyworld" minSdkVersion 15 targetSdkVersion 20 versionCode 1 versionName "1.0" } buildTypes { release { runProguard false proguardFiles getDefaultProguardFile('proguard-android.txt'), 
 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) } buildscript { repositories { jcenter() } dependencies { classpath ‘com.android.tools.build:gradle:1.2.3’ classpath ‘org.codehaus.groovy:gradle-groovy-android-plugin:0.3.6’ } }
  16. 27 Modify your app’s Gradle build apply plugin: 'com.android.application' android

    { compileSdkVersion 20 buildToolsVersion "20.0.0" defaultConfig { applicationId "com.appspot.glaforge.hellogroovyworld" minSdkVersion 15 targetSdkVersion 20 versionCode 1 versionName "1.0" } buildTypes { release { runProguard false proguardFiles getDefaultProguardFile('proguard-android.txt'), 
 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) } apply plugin: ‘groovyx.grooid.groovy-android’ buildscript { repositories { jcenter() } dependencies { classpath ‘com.android.tools.build:gradle:1.2.3’ classpath ‘org.codehaus.groovy:gradle-groovy-android-plugin:0.3.6’ } }
  17. 27 Modify your app’s Gradle build apply plugin: 'com.android.application' android

    { compileSdkVersion 20 buildToolsVersion "20.0.0" defaultConfig { applicationId "com.appspot.glaforge.hellogroovyworld" minSdkVersion 15 targetSdkVersion 20 versionCode 1 versionName "1.0" } buildTypes { release { runProguard false proguardFiles getDefaultProguardFile('proguard-android.txt'), 
 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) } compile 'org.codehaus.groovy:groovy:2.4.3:grooid' apply plugin: ‘groovyx.grooid.groovy-android’ buildscript { repositories { jcenter() } dependencies { classpath ‘com.android.tools.build:gradle:1.2.3’ classpath ‘org.codehaus.groovy:gradle-groovy-android-plugin:0.3.6’ } }
  18. 28 Java to Groovy… • Rename your activity from .java

    to .groovy • Remove… • public and return keywords • some parentheses • use the property notation • getMenuInflater() becomes menuInflater • use interpolated strings • and more.
  19. 29 Java to Groovy… import android.app.Activity import android.os.Bundle import android.view.*

    class HelloGroovyWorld extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate savedInstanceState setContentView R.layout.activity_hello_groovy_world } boolean onCreateOptionsMenu(Menu menu) { menuInflater.inflate R.menu.hello_groovy_world, menu true } boolean onOptionsItemSelected(MenuItem item) { int id = item.itemId if (id == R.id.action_settings) { return true } super.onOptionsItemSelected item }
  20. 31 What about weight? On a demo application (conference agenda)

    Artifact Size Groovy JAR 4.5 MB Generated APK 2 MB After ProGuard 1 MB (8K methods)
  21. 32 ProGuard rules -dontobfuscate -keep class org.codehaus.groovy.vmplugin.** -keep class org.codehaus.groovy.runtime.dgm*

    -keepclassmembers class org.codehaus.groovy.runtime.dgm* { *; } -keepclassmembers class ** implements org.codehaus.groovy.runtime.GeneratedClosure { *; } -dontwarn org.codehaus.groovy.** -dontwarn groovy**
  22. 35 First attempt — DiscoBot Compile-time Runtime .class .jar .dex

    .apk « Ruboto » approach, slow & inneficient
  23. 40 From Java to Groovy… public class Greeter { private

    String owner; public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } public String greet(String name) { return "Hello " + name + ", I am " + owner; } } Greeter greeter = new Greeter(); greeter.setOwner("Guillaume"); System.out.println(greeter.greet("Marion"));
  24. 40 From Java to Groovy… public class Greeter { private

    String owner; public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } public String greet(String name) { return "Hello " + name + ", I am " + owner; } } Greeter greeter = new Greeter(); greeter.setOwner("Guillaume"); System.out.println(greeter.greet("Marion")); class Greeter { String owner String greet(String name) { "Hello ${name}, I am ${owner}" } } def greeter = new Greeter(owner: "Guillaume") println greeter.greet("Marion")
  25. 41 Special Groovy syntax sugar — special operators // Groovy

    truth // if (s != null && s.length() > 0) {...} if (s) { ... } // Elvis def name = person.name ?: "unknown" // save navigation order?.lineItem?.item?.name
  26. 41 Special Groovy syntax sugar — special operators // Groovy

    truth // if (s != null && s.length() > 0) {...} if (s) { ... } // Elvis def name = person.name ?: "unknown" // save navigation order?.lineItem?.item?.name if (person.name != null && person.name.length() > 0)
  27. 41 Special Groovy syntax sugar — special operators // Groovy

    truth // if (s != null && s.length() > 0) {...} if (s) { ... } // Elvis def name = person.name ?: "unknown" // save navigation order?.lineItem?.item?.name Copied by Swift, C# 
 and CoffeeScript!
  28. 41 Special Groovy syntax sugar — special operators // Groovy

    truth // if (s != null && s.length() > 0) {...} if (s) { ... } // Elvis def name = person.name ?: "unknown" // save navigation order?.lineItem?.item?.name Anything null in the chain? Null
  29. 41 Special Groovy syntax sugar — special operators // Groovy

    truth // if (s != null && s.length() > 0) {...} if (s) { ... } // Elvis def name = person.name ?: "unknown" // save navigation order?.lineItem?.item?.name Better NPEs: Cannot get property name on null object
  30. 42 Special Groovy syntax sugar — collections, regex, closures //

    lists def list = [1, 2, 3, 4, 5] // maps def map = [a: 1, b: 2, c: 3] // regular expressions def regex = ~/.*foo.*/ // ranges def range = 128..255 // closures def adder = { a, b -> a + b }
  31. 44 Groovy builders — XML, JSON… Android views & layouts!

    import groovy.json.* def json = new JsonBuilder() json.person { name 'Guillaume' age 37 daughters 'Marion', 'Erine' address { street '1 Main St' zip 75001 city 'Paris' } }
  32. 44 Groovy builders — XML, JSON… Android views & layouts!

    import groovy.json.* def json = new JsonBuilder() json.person { name 'Guillaume' age 37 daughters 'Marion', 'Erine' address { street '1 Main St' zip 75001 city 'Paris' } } { "person": { "name": "Guillaume", "age": 37, "daughters": [ "Marion", "Erine" ], "address": { "street": "1 Main St", "zip": 75001, "city": "Paris" } } }
  33. 45 HTTP GET and JSON parsing in 4 lines import

    groovy.json.* def url = "https://api.github.com/repos" +
 "groovy/groovy-core/commits" def commits = new JsonSlurper().parseText(url.toURL().text) assert commits[0].commit.author.name == 'Cedric Champeau'
  34. 45 HTTP GET and JSON parsing in 4 lines import

    groovy.json.* def url = "https://api.github.com/repos" +
 "groovy/groovy-core/commits" def commits = new JsonSlurper().parseText(url.toURL().text) assert commits[0].commit.author.name == 'Cedric Champeau' An HTTP GET in a one-liner!
  35. 45 HTTP GET and JSON parsing in 4 lines import

    groovy.json.* def url = "https://api.github.com/repos" +
 "groovy/groovy-core/commits" def commits = new JsonSlurper().parseText(url.toURL().text) assert commits[0].commit.author.name == 'Cedric Champeau' No complex object graph marshalling!
  36. 46 Nice assertion failures def (a, b, c) = [20,

    30, 40] assert a * (b - 1) / 10 == 3 * c / 2 + 1
  37. 46 Nice assertion failures def (a, b, c) = [20,

    30, 40] assert a * (b - 1) / 10 == 3 * c / 2 + 1 Assertion failed: assert a * (b - 1) / 10 == 3 * c / 2 + 1 | | | | | | | | | | | 580| 29 58 false| | 60 61 20 30 | 40 120 at script1.run(script1.groovy:3)
  38. 48 Dagger 2 and Groovy • Groovy’s « joint compilation

    » palliates lack of APT • generates Java stubs with annotations • Java stubs processed by Dagger’s processor • http://bit.ly/dagger-groovy
  39. 49 AST transformations — only a short list! Code generation

    • @ToString • @EqualsAndHashCode • @Canonical • @TupleConstructor • @InheritConstructors • @Category • @Lazy • @Sortable • @Builder Class design • @Delegate • @Immutable • @Memoized • @Singleton Compiler directives • @AnnotationCollector • @DelegatesTo • @TypeChecked • @CompileStatic • @TailRecursive
  40. 50 AST transformations — @Immutable Implement immutability by the book

    • final class • tuple-style constructor • private final backing fields • defensive copying of collections • equals() and hashCode() methods • toString() method • ...
  41. 51 AST transformations — @Immutable • A person class •

    a String name • an int age public final class Person { private final String name; private final int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } public int hashCode() { return age + 31 * name.hashCode(); } public boolean equals(Object other) { if (other == null) { return false; } if (this == other) { return true; } if (Person.class != other.getClass()) { return false; } Person otherPerson = (Person)other; if (!name.equals(otherPerson.getName()) { return false; } if (age != otherPerson.getAge()) { return false; } return true; } public String toString() { return "Person(" + name + ", " + age + ")"; } }
  42. 52 AST transformations — @Immutable • A person class •

    a String name • an int age import groovy.transform.* @Immutable class Person { String name int age }
  43. 54 Traits • Like interfaces, but with method bodies –

    similar to Java 8 interface default methods • Elegant way to compose behavior – multiple inheritance without the « diamond » problem • Traits can also be stateful – traits can have properties like normal classes • Compatible with static typing and static compilation – class methods from traits also visible from Java classes • Also possible to implement traits at runtime
  44. 55 Traits — example: melix / speakertime @CompileStatic class PresentationActivity

    extends Activity implements GoogleApiProvider { // ... protected void onCreate(Bundle savedState) { super.onCreate(savedState) // ... createGoogleApi() } protected void onStart() { super.onStart() connectGoogleApi() } protected void onStop() { super.onStop() disconnectGoogleApi() } // ... @CompileStatic trait GoogleApiProvider { GoogleApiClient googleApiClient void createGoogleApi() { /* ... */ } void connectGoogleApi() { /* ... */ } void disconnectGoogleApi() { /* ... */ } }
  45. 57 Event listeners button.onClickListener = { Log.i(tag, "clicked!") } button.setOnClickListener(new

    OnClickListener() { @Override public void onClick(View view) { Log.i(tag, "clicked!") } })
  46. 59 Groovy Development Kit methods new Thread() { public void

    run() { // … } }.start(); Thread.start { // … }
  47. 60 Groovy’s with {} method view = new TextView(context); view.setText(name);

    view.setTextSize(16f); view.setTextColor(Color.WHITE);
  48. 60 Groovy’s with {} method view = new TextView(context); view.setText(name);

    view.setTextSize(16f); view.setTextColor(Color.WHITE); view = new TextView(context) view.with { text = name textSize = 16f textColor = Color.WHITE }
  49. 61 Groovy Truth and GStrings EditText phone = (EditText)findViewById(R.id.phone); if

    ((phone.getText() != null) && !phone.getText().equals("")) { String phoneString = parsePhone(phone.getText().toString()); Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + phoneString)); startActivity(intent); }
  50. 61 Groovy Truth and GStrings EditText phone = (EditText)findViewById(R.id.phone); if

    ((phone.getText() != null) && !phone.getText().equals("")) { String phoneString = parsePhone(phone.getText().toString()); Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + phoneString)); startActivity(intent); } EditText phone = findViewById(R.id.phone) if (phone.text) { def phoneString = parsePhone(phone.text) def intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:${phoneString}")) startActivity intent }
  51. 62 Resource handling with closures and file methods File f

    = new File("/sdcard/dir/f.txt"); if (f.exists() && f.canRead()) { FileInputStream fis = null; try { fis = new FileInputStream(rFile); byte[] bytes = new byte[fis.available()]; while (fis.read(bytes) != -1) {} textView.setText(new String(bytes)); } catch (IOException e) { // log and or handle } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { // swallow } } } }
  52. 62 Resource handling with closures and file methods File f

    = new File("/sdcard/dir/f.txt"); if (f.exists() && f.canRead()) { FileInputStream fis = null; try { fis = new FileInputStream(rFile); byte[] bytes = new byte[fis.available()]; while (fis.read(bytes) != -1) {} textView.setText(new String(bytes)); } catch (IOException e) { // log and or handle } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { // swallow } } } } def f = new File("/sdcard/dir/f.txt") if (f.exists() && f.canRead()) { f.withInputStream { fis -> def bytes = new byte[fis.available()] while (fis.read(bytes) != -1) {} textView.text = new String(bytes) } }
  53. 62 Resource handling with closures and file methods File f

    = new File("/sdcard/dir/f.txt"); if (f.exists() && f.canRead()) { FileInputStream fis = null; try { fis = new FileInputStream(rFile); byte[] bytes = new byte[fis.available()]; while (fis.read(bytes) != -1) {} textView.setText(new String(bytes)); } catch (IOException e) { // log and or handle } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { // swallow } } } } def f = new File("/sdcard/dir/f.txt") if (f.exists() && f.canRead()) { f.withInputStream { fis -> def bytes = new byte[fis.available()] while (fis.read(bytes) != -1) {} textView.text = new String(bytes) } } def f = new File("/sdcard/dir/f.txt") if (f.exists() && f.canRead()) { textView.text = f.text }
  54. 64 What else? • Projects with dedicated Groovy support •

    Swiss Knife • similar to Butter Knife & 
 Android Annotations • Grooid Playground • a builder for creating views
 in a hierarchical & visual manner
  55. 65 Swiss Knife — Arasthel / SwissKnife Like Butter Knife

    & Android Annotations, but for Groovy
  56. 66 Swiss Knife class MyActivity extends Activity { @OnClick(R.id.button) void

    onButtonClicked(Button button) { Toast.makeText(this, "Clicked", Toast.LENGTH_SHOT).show() } @OnBackground void doSomeProcessing(URL url) { // Contents will be executed on background } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate savedInstanceState setContentView R.layout.activity_main SwissKnife.inject this } }
  57. 66 Swiss Knife class MyActivity extends Activity { @OnClick(R.id.button) void

    onButtonClicked(Button button) { Toast.makeText(this, "Clicked", Toast.LENGTH_SHOT).show() } @OnBackground void doSomeProcessing(URL url) { // Contents will be executed on background } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate savedInstanceState setContentView R.layout.activity_main SwissKnife.inject this } } View injection
  58. 66 Swiss Knife class MyActivity extends Activity { @OnClick(R.id.button) void

    onButtonClicked(Button button) { Toast.makeText(this, "Clicked", Toast.LENGTH_SHOT).show() } @OnBackground void doSomeProcessing(URL url) { // Contents will be executed on background } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate savedInstanceState setContentView R.layout.activity_main SwissKnife.inject this } } Execute code outside the UI thread
  59. 66 Swiss Knife class MyActivity extends Activity { @OnClick(R.id.button) void

    onButtonClicked(Button button) { Toast.makeText(this, "Clicked", Toast.LENGTH_SHOT).show() } @OnBackground void doSomeProcessing(URL url) { // Contents will be executed on background } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate savedInstanceState setContentView R.layout.activity_main SwissKnife.inject this } } Injection point of views and callbacks
  60. 67 Swiss Knife — available annotations • @OnUIThread • @OnBackground

    • @OnClick • @OnLongClick • @OnItemClick • @OnItemLongClick • @SaveInstance • @Parcelable • @OnItemSelected • @OnChecked • @OnFocusChanged • @OnTouch • @OnPageChanged • @OnTextChanged • @OnEditorAction • @Extra • @Res
  61. 67 Swiss Knife — available annotations • @OnUIThread • @OnBackground

    • @OnClick • @OnLongClick • @OnItemClick • @OnItemLongClick • @SaveInstance • @Parcelable • @OnItemSelected • @OnChecked • @OnFocusChanged • @OnTouch • @OnPageChanged • @OnTextChanged • @OnEditorAction • @Extra • @Res AST transformations
  62. 68 Swiss Knife — extension methods • Thanks to Groovy’s

    method extension mechanism, 
 can enrich any type with your own methods • DSL methods for • views • context • fragment • event • bundle
  63. 68 Swiss Knife — extension methods • Thanks to Groovy’s

    method extension mechanism, 
 can enrich any type with your own methods • DSL methods for • views • context • fragment • event • bundle Adds missing methods you wish the Android SDK had!
  64. 80 Grooid Playground — karfunkel / grooid-playground • When you

    need to build dynamic views (ie. not XML-driven) def layout = new RelativeLayout(this) layout.width = MATCH_PARENT layout.height = MATCH_PARENT layout.setPaddingRelative(64, 16, 64, 16) def textView = new TextView(this) textView.width = MATCH_PARENT textView.height = 20 textView.setText R.string.hello_world layout.addView textView setContentView(layout)
  65. 80 Grooid Playground — karfunkel / grooid-playground • When you

    need to build dynamic views (ie. not XML-driven) def layout = new RelativeLayout(this) layout.width = MATCH_PARENT layout.height = MATCH_PARENT layout.setPaddingRelative(64, 16, 64, 16) def textView = new TextView(this) textView.width = MATCH_PARENT textView.height = 20 textView.setText R.string.hello_world layout.addView textView setContentView(layout) Imperative
  66. 80 Grooid Playground — karfunkel / grooid-playground • When you

    need to build dynamic views (ie. not XML-driven) setContentView new AndroidBuilder().build(this) { relativeLayout(width: MATCH_PARENT, height: MATCH_PARENT, padding: [64.dip, 16.dip]) { textView(width: MATCH_PARENT, height: 20.dip, text: R.string.hello_world) } } Declarative, hierarchical, visual
  67. 82 Programming your Android applications in Groovy • You can

    use Groovy to code Android apps! • use Groovy 2.4.3 • prefer @CompileStatic • Two great posts to get started: • http://bit.ly/grooid-1 • http://bit.ly/grooid-2 • Gradle plugin support: • http://bit.ly/grooid-gradle
  68. 86 Summary • Groovy can make your Android code… •

    more concise & readable • more maintainable • Without compromising • type safety • speed
  69. 91 Image credits • Stone age • http://3.bp.blogspot.com/-lMDyyBdibG4/UxDYLq5ItiI/AAAAAAAALf8/HPpKRixHOIE/s1600/Neanderthal+Man+1.jpg • Many

    thanks • http://www.trys.ie/wp-content/uploads/2013/06/many-thanks.jpg • Disclaimer • http://3.bp.blogspot.com/-RGnBpjXTCQA/Tj2h_JsLigI/AAAAAAAABbg/AB5ZZYzuE5w/s1600/disclaimer.jpg • Builders • http://detroittraining.com/wp-content/uploads/Construction-Women2.jpg • Newbie • http://contently.com/strategist/wp-content/uploads/2014/05/bigstock-Shocked-Computer-Nerd-1520709.jpg • Alternative rock band • http://upload.wikimedia.org/wikipedia/commons/b/b2/Arcade_Fire_live_20050315.ext.jpg • Twins • http://images.doctissimo.fr/1/jumeaux-jumelles/photo/hd/4424027442/137540656da/jumeaux-jumelles-jumeaux-jumelles-2-big.jpg • Annotations • http://3.bp.blogspot.com/-f94n9BHko_s/T35ELs7nYvI/AAAAAAAAAKg/qe06LRPH9U4/s1600/IMG_1721.JPG • Apple & Orange • http://cdni.wired.co.uk/1920x1280/a_c/comparison.jpg • We need you • http://media.moddb.com/images/mods/1/14/13849/WickedSunshine_UncleSam_Blank_800x1000.png
  70. 92 Image credits • Macarons • http://www.thatfoodcray.com/wp-content/uploads/2013/12/that-food-cray-pierre-herme-paris-hong-kong-christmas-flavors-foie-gras-truffle-9.jpg? 4dd047 • Bridge

    • http://tenspeedhero.com/wp-content/uploads/Bridge_3.jpg • Swiss knife • http://www.formengifts.com/wp-content/uploads/2011/04/swiss-army-champ-multitool-knife2.jpg • Hello World • http://www.warrenpriestley.com/wp-content/uploads/2012/01/helloworld.gif • George Clooney • http://www.brewville.ca/wp-content/uploads/2014/05/nespresso-george-clooney.jpg • Weight scale • http://images.wisegeek.com/person-stands-on-scale.jpg • Nexus 5 • http://fs01.androidpit.info/userfiles/2692059/image/Blog/nexus-5-weiss_628.jpg • File icon • https://dl.vecnet.org/assets/default.png • Gear • http://www.pvzgears.com/wp-content/uploads/2013/03/gears2a.png • Sugar • http://images.wisegeek.com/cane-sugar.jpg