Save 37% off PRO during our Black Friday Sale! »

Groovy and Android: a winning pair

Groovy and Android: a winning pair

Talk given at #Devoxx Belgium 2014 about using the Groovy language to develop Android applications.

F929f5d80ef8a23b67ad8ac6f08416cd?s=128

Cédric Champeau

November 14, 2014
Tweet

Transcript

  1. @YourTwitterHandle #DV14 #YourTag @CedricChampeau #DV14 #GroovyAndroid Groovy & Android A

    winning pair • Cédric Champeau • Pivotal
  2. #DV14 #GroovyAndroid @CedricChampeau whoami.groovy def speaker = new Speaker( name:

    'Cedric Champeau', employer: 'Pivotal', occupation: 'Core Groovy committer', successes: ['Static type checker', 'Static compilation', 'Traits', 'Markup template engine', 'DSLs'], twitter: '@CedricChampeau', github: 'melix', extraDescription: '''Groovy in Action 2 co- author Misc OSS contribs (Gradle plugins, deck2pdf, jlangdetect, ...)''' ) 2
  3. #DV14 #GroovyAndroid @CedricChampeau Why Android? • Uses a JVM •

    SDK is free • Tooling also freely available (Android Studio) • Swift anyone? 3
  4. #DV14 #GroovyAndroid @CedricChampeau Why Groovy? • Built on top of

    the shoulders of a Giant (Java) • Runs a JVM • Android developers shouldn't be suffering • Java on Android is very verbose • And the main development language on the platform • Multi-faceted language • OO, Imperative, functional, scripting, dynamic, static, … • Straightforward integration with Java 4
  5. #DV14 #GroovyAndroid @CedricChampeau Why Groovy? button.setOnClickListener(new View.OnClickListener() { @Override void

    onClick(View v) { startActivity(intent); } }) 5 button.onClickListener = { startActivity(intent) }
  6. #DV14 #GroovyAndroid @CedricChampeau Why Groovy? @RestableEntity @ToString class User {

    String name String phone String avatar Integer balance static constraints = { name pattern: ~/[a-zA-Z]+/, min: 3, max: 5, blank: false, nullable: false phone pattern: ~/\d+/, size: 2..4 balance range: 10..100 } } 6 Groovy Beans Grails-like entities
  7. #DV14 #GroovyAndroid @CedricChampeau def user = new User(name: 'Name') form(R.id.user_form,

    user) { form -> editText(R.id.user_name).attach('name') editText(R.id.user_phone).attach('phone') editText(R.id.user_balance).attach('balance') form.submit(R.id.submit_button) { if (form.object.validate()) { this.showToast('Validated with success!') } else { form.object.errors.each { this.showToast(it.toString()) } } } } 7 declarative code Why Groovy?
  8. #DV14 #GroovyAndroid @CedricChampeau Intent viewIntent = new Intent(this, WearPresentationActivity.class); PendingIntent

    viewPendingIntent = PendingIntent.getActivity( this, 0, viewIntent, FLAG_UPDATE_CURRENT); NotificationCompat.BigTextStyle bigStyle = new NotificationCompat.BigTextStyle(); bigStyle.bigText("Time left for your presentation: "+timeLeft+"\n"+ "Elapsed time: "+rounded+"%"); NotificationCompat.Builder notificationBuilder= new NotificationCompat.Builder(this); notificationBuilder.setSmallIcon(R.drawable.ic_action_alarms) .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.drawable.speaker)) .setContentTitle("Time left") .setContentText(timeLeft+" (Elapsed: "+rounded+"%)") .setContentIntent(viewPendingIntent) .setStyle(bigStyle); NotificationManagerCompat notificationManager= NotificationManagerCompat.from(this); notificationManager.notify(NOTIFICATION_ID,notificationBuilder.build()); 8 Why Groovy?
  9. #DV14 #GroovyAndroid @CedricChampeau notify(NOTIFICATION_ID) { smallIcon = R.drawable.ic_action_alarms largeIcon =

    cachedBitmap contentTitle = 'Time left' contentText = "$timeLeft (Elapsed: ${rounded}%)" contentIntent = pendingActivityIntent(0, intent(WearPresentationActivity), FLAG_UPDATE_CURRENT) ongoing = true style = bigTextStyle { bigText """Time left for your presentation: $timeLeft Elapsed time: ${rounded}%) """ } } 9 Why Groovy?
  10. #DV14 #GroovyAndroid @CedricChampeau @CompileStatic class ContextGroovyMethods { static NotificationManagerCompat getCompatNotificationManager(Context

    self) { NotificationManagerCompat.from(self) } static void notify(Context self, int notificationId, Notification notification) { getCompatNotificationManager(self).notify(notificationId, notification) } static Notification notification(Context self, @DelegatesTo(NotificationCompat.Builder) Closure notificationSpec) { def builder = new NotificationCompat.Builder(self) builder.with(notificationSpec) builder.build() } static void notify(Context self, int notificationId, @DelegatesTo(NotificationCompat.Builder) Closure notificationSpec) { notify(self, notificationId, notification(self, notificationSpec)) } ... } 10 Why Groovy?
  11. #DV14 #GroovyAndroid @CedricChampeau private @Lazy Bitmap cachedBitmap = BitmapFactory.decodeResource(resources, R.drawable.speaker)

    11 Why Groovy?
  12. #DV14 #GroovyAndroid @CedricChampeau 12 SpeakerTime github.com/melix/speakertime

  13. #DV14 #GroovyAndroid @CedricChampeau Groovy on Android: the problems • Groovy

    is a dynamic language • Not everything done at compile time • Intensive use of reflection • Potentially slow invocation pathes • Battery? • Bytecode is different • Classes at runtime? 13
  14. #DV14 #GroovyAndroid @CedricChampeau • Not all classes are available •

    java.bean.xxx very problematic • Multiple runtimes • Dalvik • ART • Behavior not the same as the standard JVM 14 Groovy on Android: the problems
  15. #DV14 #GroovyAndroid @CedricChampeau Groovy on Android: discobot • Early days

    • Written in 2011 • Fork of Groovy 1.7 • Capable of running scripts at runtime • but slow... 15
  16. #DV14 #GroovyAndroid @CedricChampeau Groovy on Android: dex files • Dalvik

    VM = alternative bytecode • Groovy generates JVM bytecode • Translation done through dex • No native support for generating classes at runtime 16
  17. #DV14 #GroovyAndroid @CedricChampeau Compiling an Android application • Classic process

    17
  18. #DV14 #GroovyAndroid @CedricChampeau • Classic process for a Groovy application

    18 Compiling an Android application
  19. #DV14 #GroovyAndroid @CedricChampeau Discobot process • Write Groovy bytes to

    a file • Package those into a jar • Use a special classloader to load the class • Enjoy! 19
  20. #DV14 #GroovyAndroid @CedricChampeau Compiling an Android application • Runtime generation

    of classes 20
  21. #DV14 #GroovyAndroid @CedricChampeau Discobot process • Works, but very slow

    • Lots of I/O involved • What about ASMDex? • Same approach used by Ruboto • Nice proof of concept 21
  22. #DV14 #GroovyAndroid @CedricChampeau Redefinining objectives

  23. #DV14 #GroovyAndroid @CedricChampeau Groovy 2.4: Objectives for Android • Supporting

    Android in the standard distribution • Building a full Android application in Groovy • Main focus on @CompileStatic • Optional use of dynamic Groovy 23
  24. #DV14 #GroovyAndroid @CedricChampeau Groovy 2.4: Objectives for community • Community

    is a major strenght of Groovy • We need you for Android too! • Bring the goodness of Groovy to Android • Invent new frameworks! 24
  25. #DV14 #GroovyAndroid @CedricChampeau Does it work?

  26. #DV14 #GroovyAndroid @CedricChampeau 26

  27. #DV14 #GroovyAndroid @CedricChampeau 27 github.com/melix/grooidshell-example

  28. #DV14 #GroovyAndroid @CedricChampeau Requirements • Gradle • Android Studio •

    Or your favorite editor... • Groovy 2.4.0-beta-3 • A good tutorial on Android... 28
  29. #DV14 #GroovyAndroid @CedricChampeau Groovy 2.4 Android Support • Must use

    a specific Android jar • Use of the grooid classifier • Replaces java.beans use with openbeans • Workarounds for Android specific behavior • Reduced number of methods in bytecode • Important for the 64k limit of dex files 29
  30. #DV14 #GroovyAndroid @CedricChampeau Gradle plugin • Gradle is the new

    default build system for Android • apply plugin: 'com.android.application' • Uses a non standard compilation process • Without Groovy specific plugin, lots of trickery involved • Thus apply plugin: apply plugin: 'me.champeau.gradle.groovy-android' • Supports both the application and library plugins 30
  31. #DV14 #GroovyAndroid @CedricChampeau Gradle plugin buildscript { repositories { jcenter()

    } dependencies { classpath 'com.android.tools.build:gradle:0.14.0' classpath 'me.champeau.gradle:gradle-groovy-android-plugin:0.3.4' } } apply plugin: 'me.champeau.gradle.groovy-android' dependencies { compile 'org.codehaus.groovy:groovy:2.4.0-beta-3:grooid' } 31
  32. #DV14 #GroovyAndroid @CedricChampeau Then code! @CompileStatic @ToString(includeNames = true) @EqualsAndHashCode

    class Session { Long id Long speakerId Slot slot String title String summary List<String> tags } 32
  33. #DV14 #GroovyAndroid @CedricChampeau Groovifying Android APIs 33 class FeedTask extends

    AsyncTask<String, Void, String> { protected String doInBackground(String... params) { // very long boilerplate code.... } @Override protected void onPostExecute(String s) { mTextView.setText(s); } }
  34. #DV14 #GroovyAndroid @CedricChampeau Groovifying Android APIs 34 Fluent.async { def

    json = new JsonSlurper().parse([:], new URL('http://path/to/feed'), 'utf-8') json.speakers.join(' ') } then { mTextView.text = it }
  35. #DV14 #GroovyAndroid @CedricChampeau Performance?

  36. #DV14 #GroovyAndroid @CedricChampeau System resources • Example of the GR8Conf

    Agenda application • Groovy jar: 4.5MB • Application size: 2MB! • After ProGuard: only 1MB! • ~8.2MB of RAM! (but lots of images) 36
  37. #DV14 #GroovyAndroid @CedricChampeau Community

  38. #DV14 #GroovyAndroid @CedricChampeau Community projects • Community is more important

    than the language • New frameworks to invent • Some already did! 38
  39. #DV14 #GroovyAndroid @CedricChampeau SwissKnife • Similar to Android Annotations and

    ButterKnife • Based on AST transformations • View injection • Threading model • Works with annotations to generate code 39
  40. #DV14 #GroovyAndroid @CedricChampeau SwissKnife 40 class MyActivity extends Activity {

    @ViewById(R.id.myField) TextField mTextField @OnClick(R.id.button) void onButtonClicked(Button button) { Toast.makeText(this, "Button 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) contentView = R.layout.activity_main // This must be called for injection of views and callbacks to take place SwissKnife.inject(this) } }
  41. #DV14 #GroovyAndroid @CedricChampeau Grooid Tools 41 View view = new

    AndroidBuilder().build(this) { relativeLayout(width: MATCH_PARENT, height: MATCH_PARENT, padding: [dp(64), dp(16)]) { textView(width: MATCH_PARENT, height: dp(20), text: R.string.hello_world) } } • Builders for views • Experimental • https://github.com/karfunkel/grooid-tools
  42. #DV14 #GroovyAndroid @CedricChampeau Potential issues 42 • Performance of dynamic

    Groovy on low end-devices • Use @CompileStatic whenever possible • The infamous 64k method count • Use ProGuard and multidex support • Tooling support • Groovy not fully supported by Android Studio • Google support • Android Gradle plugin updates are very frequent
  43. #DV14 #GroovyAndroid @CedricChampeau “Best of all, I expect to try

    to update Android Studio right before the talk, so I have the latest possible version in the so­called Canary channel. What could possibly go wrong?” Ken Kousen, September 10th, 2014
  44. #DV14 #GroovyAndroid @CedricChampeau Other ideas 44 • Dagger-like dependency injection

    framework? • Data binding APIs • Improved reactive APIs • You can already use Reactor or RxJava
  45. #DV14 #GroovyAndroid @CedricChampeau Future is now! 45 • New York

    Times next app will be written in Groovy!
  46. #DV14 #GroovyAndroid @CedricChampeau 46 @CedricChampeau melix http://melix.github.io/blog