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 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, ...)''' )
  2. #GroovyAndroid @CedricChampeau 3/47 Why Android? • Uses a JVM •

    SDK is free • Tooling also freely available (Android Studio) • Swift anyone?
  3. #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
  4. #GroovyAndroid @CedricChampeau 5/47 Why Groovy? button.setOnClickListener(new View.OnClickListener() { @Override void

    onClick(View v) { startActivity(intent); } }) button.onClickListener = { startActivity(intent) }
  5. #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
  6. #GroovyAndroid @CedricChampeau 7/47 @CompileStatic @SelfType(Context) trait GoogleApiProvider { GoogleApiClient googleApiClient

    void createGoogleApi() { googleApiClient = new GoogleApiClient.Builder(this) .addApi(Wearable.API) .build() } ... } Why Groovy?
  7. #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?
  8. #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?
  9. #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?
  10. #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?
  11. #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?
  12. #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
  13. #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
  14. #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
  15. #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!
  16. #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!
  17. #GroovyAndroid @CedricChampeau 26/47 Requirements • Gradle • Android Studio •

    Or your favorite editor... • Groovy 2.4.0 (“grooid”) • A good tutorial on Android...
  18. #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
  19. #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
  20. #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' }
  21. #GroovyAndroid @CedricChampeau 30/47 Then code! @CompileStatic @ToString(includeNames = true) @EqualsAndHashCode

    class Session { Long id Long speakerId Slot slot String title String summary List<String> tags }
  22. #GroovyAndroid @CedricChampeau 31/47 Groovifying Android APIs 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); } }
  23. #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 }
  24. #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)
  25. #GroovyAndroid @CedricChampeau 36/47 Community projects • Community is more important

    than the language • New frameworks to invent • Some already did!
  26. #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
  27. #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) } }
  28. #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) }
  29. #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; } // ... }
  30. #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
  31. #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
  32. #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
  33. #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