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

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.

Cédric Champeau

November 14, 2014
Tweet

More Decks by Cédric Champeau

Other Decks in Programming

Transcript

  1. @YourTwitterHandle
    #DV14 #YourTag @CedricChampeau
    #DV14 #GroovyAndroid
    Groovy & Android
    A winning pair
    • Cédric Champeau
    • Pivotal

    View Slide

  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

    View Slide

  3. #DV14 #GroovyAndroid @CedricChampeau
    Why Android?
    • Uses a JVM
    • SDK is free
    • Tooling also freely available (Android Studio)
    • Swift anyone?
    3

    View Slide

  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

    View Slide

  5. #DV14 #GroovyAndroid @CedricChampeau
    Why Groovy?
    button.setOnClickListener(new View.OnClickListener() {
    @Override
    void onClick(View v) {
    startActivity(intent);
    }
    })
    5
    button.onClickListener = {
    startActivity(intent)
    }

    View Slide

  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

    View Slide

  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?

    View Slide

  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?

    View Slide

  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?

    View Slide

  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?

    View Slide

  11. #DV14 #GroovyAndroid @CedricChampeau
    private @Lazy Bitmap cachedBitmap =
    BitmapFactory.decodeResource(resources, R.drawable.speaker)
    11
    Why Groovy?

    View Slide

  12. #DV14 #GroovyAndroid @CedricChampeau
    12
    SpeakerTime
    github.com/melix/speakertime

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  17. #DV14 #GroovyAndroid @CedricChampeau
    Compiling an Android application
    • Classic process
    17

    View Slide

  18. #DV14 #GroovyAndroid @CedricChampeau
    • Classic process for a Groovy application
    18
    Compiling an Android application

    View Slide

  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

    View Slide

  20. #DV14 #GroovyAndroid @CedricChampeau
    Compiling an Android application
    • Runtime generation of classes
    20

    View Slide

  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

    View Slide

  22. #DV14 #GroovyAndroid @CedricChampeau
    Redefinining objectives

    View Slide

  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

    View Slide

  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

    View Slide

  25. #DV14 #GroovyAndroid @CedricChampeau
    Does it work?

    View Slide

  26. #DV14 #GroovyAndroid @CedricChampeau
    26

    View Slide

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

    View Slide

  28. #DV14 #GroovyAndroid @CedricChampeau
    Requirements
    • Gradle
    • Android Studio
    • Or your favorite editor...
    • Groovy 2.4.0-beta-3
    • A good tutorial on Android...
    28

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  32. #DV14 #GroovyAndroid @CedricChampeau
    Then code!
    @CompileStatic
    @ToString(includeNames = true)
    @EqualsAndHashCode
    class Session {
    Long id
    Long speakerId
    Slot slot
    String title
    String summary
    List tags
    }
    32

    View Slide

  33. #DV14 #GroovyAndroid @CedricChampeau
    Groovifying Android APIs
    33
    class FeedTask extends AsyncTask {
    protected String doInBackground(String... params) {
    // very long boilerplate code....
    }
    @Override
    protected void onPostExecute(String s) {
    mTextView.setText(s);
    }
    }

    View Slide

  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
    }

    View Slide

  35. #DV14 #GroovyAndroid @CedricChampeau
    Performance?

    View Slide

  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

    View Slide

  37. #DV14 #GroovyAndroid @CedricChampeau
    Community

    View Slide

  38. #DV14 #GroovyAndroid @CedricChampeau
    Community projects
    • Community is more important than the language
    • New frameworks to invent
    • Some already did!
    38

    View Slide

  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

    View Slide

  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)
    }
    }

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  45. #DV14 #GroovyAndroid @CedricChampeau
    Future is now!
    45
    • New York Times next app will be written in Groovy!

    View Slide

  46. #DV14 #GroovyAndroid @CedricChampeau
    46
    @CedricChampeau
    melix
    http://melix.github.io/blog

    View Slide