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 MCE 2015, Warsaw, about using Groovy to develop Android applications.

Cédric Champeau

February 05, 2015
Tweet

More Decks by Cédric Champeau

Other Decks in Programming

Transcript

  1. #GroovyAndroid @CedricChampeau
    Groovy & Android
    A winning pair
    By Cédric Champeau

    View full-size slide

  2. #GroovyAndroid @CedricChampeau 2/47
    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'],
    failures: Stream.of(bugs),
    twitter: '@CedricChampeau',
    github: 'melix',
    extraDescription: '''Groovy in Action 2 co-
    author
    Misc OSS contribs (Gradle plugins,
    deck2pdf, jlangdetect, ...)'''
    )

    View full-size slide

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

    View full-size slide

  4. #GroovyAndroid @CedricChampeau 4/47
    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

    View full-size slide

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

    View full-size slide

  6. #GroovyAndroid @CedricChampeau 6/47
    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
    }
    }
    Groovy Beans
    Grails-like entities

    View full-size slide

  7. #GroovyAndroid @CedricChampeau 7/47
    @CompileStatic
    @SelfType(Context)
    trait GoogleApiProvider {
    GoogleApiClient googleApiClient
    void createGoogleApi() {
    googleApiClient = new GoogleApiClient.Builder(this)
    .addApi(Wearable.API)
    .build()
    }
    ...
    }
    Why Groovy?

    View full-size slide

  8. #GroovyAndroid @CedricChampeau 8/47
    class CountDownService extends Service implements GoogleApiProvider {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState)
    contentView = R.layout.activity_presentation
    createGoogleApi()
    }
    // ...
    }
    class StartPresentationActivity extends Activity implements GoogleApiProvider {
    // ...
    }
    class WearPresentationActivity extends Activity implements GoogleApiProvider,
    MessageApi.MessageListener {
    // ...
    }
    Why Groovy?

    View full-size slide

  9. #GroovyAndroid @CedricChampeau 9/47
    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());
    Why Groovy?

    View full-size slide

  10. #GroovyAndroid @CedricChampeau 10/47
    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}%)
    """
    }
    }
    Why Groovy?

    View full-size slide

  11. #GroovyAndroid @CedricChampeau 11/47
    @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))
    }
    ...
    }
    Why Groovy?

    View full-size slide

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

    View full-size slide

  13. #GroovyAndroid @CedricChampeau 13/47
    SpeakerTime
    github.com/melix/speakertime

    View full-size slide

  14. #GroovyAndroid @CedricChampeau 14/47
    Groovy on Android: the problems
    • Groovy is primarily a dynamic language
    • Not everything done at compile time
    • Intensive use of reflection
    • Potentially slow invocation pathes
    • Battery?
    • Bytecode is different
    • Classes at runtime?

    View full-size slide

  15. #GroovyAndroid @CedricChampeau 15/47
    • Not all classes are available
    • java.bean.xxx very problematic
    • Multiple runtimes
    • Dalvik
    • ART
    • Behavior not the same as the standard JVM
    Groovy on Android: the problems

    View full-size slide

  16. #GroovyAndroid @CedricChampeau 16/47
    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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  19. #GroovyAndroid @CedricChampeau 19/47
    Compiling an Android application
    • Runtime generation of classes

    View full-size slide

  20. #GroovyAndroid @CedricChampeau 20/47
    Classes at runtime?!
    • Works, but very slow
    • Lots of I/O involved
    • What about ASMDex?
    • Same approach used by Ruboto
    • Nice proof of concept

    View full-size slide

  21. #GroovyAndroid @CedricChampeau 21/47
    Redefinining
    objectives

    View full-size slide

  22. #GroovyAndroid @CedricChampeau 22/47
    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
    • No performance issue!

    View full-size slide

  23. #GroovyAndroid @CedricChampeau 23/47
    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!

    View full-size slide

  24. #GroovyAndroid @CedricChampeau 24/47

    View full-size slide

  25. #GroovyAndroid @CedricChampeau 25/47
    github.com/melix/grooidshell-example

    View full-size slide

  26. #GroovyAndroid @CedricChampeau 26/47
    Requirements
    • Gradle
    • Android Studio
    • Or your favorite editor...
    • Groovy 2.4.0 (“grooid”)
    • A good tutorial on Android...

    View full-size slide

  27. #GroovyAndroid @CedricChampeau 27/47
    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

    View full-size slide

  28. #GroovyAndroid @CedricChampeau 28/47
    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 use a plugin: apply plugin:
    'groovyx.grooid.groovy-android'
    • Supports both the application and library plugins

    View full-size slide

  29. #GroovyAndroid @CedricChampeau 29/47
    Gradle plugin
    buildscript {
    repositories {
    jcenter()
    }
    dependencies {
    classpath 'com.android.tools.build:gradle:0.14.0'
    classpath 'org.codehaus.groovy:gradle-groovy-android-plugin:0.3.5'
    }
    }
    apply plugin: 'groovyx.grooid.groovy-android'
    dependencies {
    compile 'org.codehaus.groovy:groovy:2.4.0:grooid'
    }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  32. #GroovyAndroid @CedricChampeau 32/47
    Groovifying Android APIs
    32
    Fluent.async {
    def json = new JsonSlurper().parse([:],
    new URL('http://path/to/feed'), 'utf-8')
    json.speakers.join(' ')
    } then {
    mTextView.text = it
    }

    View full-size slide

  33. #GroovyAndroid @CedricChampeau 33/47
    Performance?

    View full-size slide

  34. #GroovyAndroid @CedricChampeau 34/47
    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)

    View full-size slide

  35. #GroovyAndroid @CedricChampeau 35/47
    Community

    View full-size slide

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

    View full-size slide

  37. #GroovyAndroid @CedricChampeau 37/47
    SwissKnife
    • Combines ideas from Android
    Annotations and ButterKnife
    • Based on AST transformations
    • View injection
    • Threading model
    • Bundles management
    • Works with annotations to generate code

    View full-size slide

  38. #GroovyAndroid @CedricChampeau 38/47
    SwissKnife
    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 full-size slide

  39. #GroovyAndroid @CedricChampeau 39/47
    SwissKnife
    @SaveInstance
    private int myInt
    // You can also set a custom tag to your variable
    @SaveInstance("MYSTRING")
    private String myString
    @Override
    void onCreate(Bundle savedInstanceState){
    // Your previous code
    SwissKnife.restoreState(this, savedInstanceState)
    }

    View full-size slide

  40. #GroovyAndroid @CedricChampeau 40/47
    SwissKnife
    public class ParcelableClass implements Parcelable {
    private int id;
    private String name;
    public ParcelableClass(Parcel source) {
    this.id = source.readInt();
    this.name = source.readString();
    }
    public void writeToParcel(Parcel out) {
    out.writeInt(id);
    out.writeString(name);
    }
    public String getId() { return id; }
    // ...
    }

    View full-size slide

  41. #GroovyAndroid @CedricChampeau 41/47
    SwissKnife
    @Parcelable
    class ParcelableClass {
    int id
    String name
    }

    View full-size slide

  42. #GroovyAndroid @CedricChampeau 42/47
    Grooid Tools
    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 full-size slide

  43. #GroovyAndroid @CedricChampeau 43/47
    Potential issues
    • 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 full-size slide

  44. #GroovyAndroid @CedricChampeau 44/47
    “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 full-size slide

  45. #GroovyAndroid @CedricChampeau 45/47
    Other ideas
    • Dagger-like dependency injection
    framework?
    • Dagger 2 in the works...
    • Data binding APIs
    • Improved reactive APIs
    • You can already use Reactor or RxJava

    View full-size slide

  46. #GroovyAndroid @CedricChampeau 46/47
    Future is now!
    • New York Times next app being written in Groovy!

    View full-size slide

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

    View full-size slide