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 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 Slide

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

    View 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 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 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 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 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 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 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 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 Slide

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

    View Slide

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

    View 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 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 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 Slide

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

    View Slide

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

    View Slide

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

    View 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 Slide

  21. #GroovyAndroid @CedricChampeau 21/47
    Redefinining
    objectives

    View 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 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 Slide

  24. #GroovyAndroid @CedricChampeau 24/47

    View Slide

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

    View 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 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 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 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 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 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 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 Slide

  33. #GroovyAndroid @CedricChampeau 33/47
    Performance?

    View 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 Slide

  35. #GroovyAndroid @CedricChampeau 35/47
    Community

    View Slide

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

    View 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 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 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 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 Slide

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

    View 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 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 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 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 Slide

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

    View Slide

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

    View Slide