Monday, December 12, 2011

Writing Android UIs with Xtend

(Update: You can find the project on github : https://github.com/svenefftinge/xtend-android-experiments )

Xtend is a great fit for Android development. Since Xtend translates to Java source code, it's very easy to use it. After you've installed the Xtend SDK and the Android Development Tools you only need to do two things to get started.
  1. In Eclipse's preferences configure Xtend's compiler to generated to the gen/ folder.
  2. Add the Xtend lib and Google Guava to your project's classpath. 
Why is it useful?
Using a combination of Xtend's powerful extension methods and closures, it's possible to come up with a very declarative API to define UI models. It takes a couple of minutes to write an API which allows for defining a UI like the following:

class AppActivity extends Activity {
 
  override void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
  
    contentView = this.linearLayout [
   
      orientation = VERTICAL
  
      view = this.textView ("Hello Android!")
  
      view = this.button ("Click Me!") [
        onClickListener = [ 
          this.textMessage('Hello you clicked me!').show
        ]
      ]
    ]
  } 
}

The API is made available by means of a static extension import. To outline the idea, here's how the button method is defined:

def static button(Context context, String text,
      (Button)=>void initializer) {
  val result = new Button(context)
  result.text = text
  initializer.apply(result)
  return result
}

Used as an extension method button can be invoked on any instance of Context. Since the last argument is a function type it can be passed after the actual method invocation:

view = this.button ("Click Me!") [
        onClickListener = [ 
          this.textMessage('Hello you clicked me!').show
        ]
      ]

Also note how you can simply assign a closure to onClickListener (which is btw. invoking setOnClickListener()). The compiler will automatically convert it to an abstract class of View.OnClickListener.
And everything is 100% statically typed!



21 comments:

  1. How where you able to work around the error:
    Error generating final archive: Found duplicate file for APK: .settings/org.eclipse.jdt.core.prefs
    eclipse/plugins/com.google.guava_10.0.1.1.jar
    eclipse/plugins/com.google.inject_3.0.0.no_no_aop.jar

    I can get it to compile and generate the right Java code but not deploy. I can download the basic eclipse and try it since I am using the J2EE eclipse version.

    ReplyDelete
  2. I copied the needed jars to the project. Android complaints about duplicate resources. Unfortunately the sometimes exist :-(

    I pushed the correctly configured android project to github :
    https://github.com/svenefftinge/xtend-android-experiments

    ReplyDelete
  3. Thanks Sven that is exactly what I needed!! I was trying to find a duplicate file (looking to try to remove them) but instead I just needed to add the jar as dependencies in the build path.

    ReplyDelete
  4. Hi Sven,

    there is a project on github called AndroText (currently under development), which aims to generate android application based on Xtext technology, you could check it out (https://github.com/CMark/androtext), it can generate gui elements, data binding...

    ReplyDelete
  5. I noticed that the R class (generated by Android) is not available inside the Activity. In your example project, if you try to access R::layout::main for example, Xtend gives errors, even though you can add it to the generated Java class without issues. Is there a way around this problem?

    ReplyDelete
  6. Hi Toby,
    in Xtend you derefernce inner classes with a dollar sign as namespace delimiter. See the project on Github.

    https://github.com/svenefftinge/xtend-android-experiments

    ReplyDelete
  7. Hi,I'm very interested in developing android app in Xtend.

    How does closure convert to interface?
    ex:
    [this.textMessage('Hello you clicked me!').show]
    ->
    new OnClickListener() {
    public void onClick(View v) {
    // ...
    }
    }
    Where does method name(ex:'onClick') come from...?

    ReplyDelete
  8. Xtend Closures are automatically converted to anonymous classes of the expected type.

    That is :

    onClickListener = [
    this.textMessage('Hello you clicked me!').show
    ]

    really translates to the following Java code:

    setOnClickListener(new OnClickListener() {
    public void onClick(View it) {
    AppActivity.this.textMessage("Hello you clicked me!").show();
    }
    });

    ReplyDelete
  9. Hello Sven,

    thanks for you enlightening post. I'm currently aiming at writing an Android application using Xtend. Although I added the Xtend library to the classpath and exported the entry, I get an NoClassDefFound within the emulator when the generated classes try to access classes from the xtext or xtend libs. How do I correctly export the jars into the apk?

    ReplyDelete
  10. A working example project is available from https://github.com/svenefftinge/xtend-android-experiments.
    I'll update it to the upcoming release of Xtend ASAP.

    ReplyDelete
  11. Thanks, using the 2 jars from the lib and exporting them did the job for me.

    ReplyDelete
  12. A few notes in case anyone else runs into similar problems:
    - To get rid of the duplicate files problem, a quick way is to just delete these files from the JARs. I was using the JARs from the Xtend SDK in Eclipse, and I had conflicting plugin.properties and about.html. You can use a command like: for I in *jar; do zip -d $I plugin.properties about.html; done
    - Make sure the JARs are being exported in the Build path section of Project properties. There is a separate tab for Order and Export, check the checkbox next to the JARs.

    ReplyDelete
  13. I'm very enthusiastic about creating android operating system app in Xtend.from your i came to more.keep it up.

    ReplyDelete
  14. While this is neat example, it's really _not_ a good practice to define UIs this way on Android.

    Using XML resources to define UIs allows different UI layouts/sub-layouts, dimensions/font sizes, text, fragments, etc to be easily used for different device screen densities/sizes, screen orientation, locale and other properties.

    I do think xtend could be very useful for Activity definition aside from UI layout, especially once you release active annotations (I've been planning to try using them to simplify Activity definition)

    But don't create views inside the Activity like this.

    ReplyDelete
    Replies
    1. Thanks for the feedback. I'll try to come up with a better example next time. Regarding Active annotations: Could you outline what you have in mind?

      Delete
    2. I've written a Java Annotation processor that generates a superclass for an Activity (generation-gap pattern) and does a few things like

      * Specify which layout XML to use via annotation
      * Generate findViewById code (in the generated superclass) for all views with ids in the XML
      * Setup listeners (in the generated superclass) on appropriate views, and hook methods that can be overridden in the actual Activity (not really needed since they added the onClick attribute in the XML, though that's prone to typos whereas the hook methods aren't)

      It was just an example I wrote on the fly when teaching my Android class at JHU, but I'd like to expand it, and it sounds like Active Annotations with Xtend will do a few really nice things for me:

      * I won't need to develop with two copies of Eclipse open (one to write the annotation processor and another to test it)

      * I can have the project I'm testing with in the same workspace

      * I don't have to use the generated-superclass/generation-gap approach (see https://code.google.com/p/javadude/wiki/Annotations for the way I've used annotation processing in the past for code generation) I'd much rather use code generation with something like Xtend for the bean annotations as well as Android helpers.

      I'd like to use a combination of annotations and reusable template-methods to handle a lot of the boilerplate that's needed for Android coding, especially things like fragment setup to allow side-by-side and individual layouts - this can be seriously tedious and error prone, as well as associating models with a UI/database/content provider, setting up IPC and service managers, generating view holders and list adapters, and about a billion other things I get tired of repeatedly typing ;)

      Keep up the great work! Last night I used Xtext to create an play-script editor for some line-rehearsal software I wrote a few years back. Saved me a ton of time proofreading the script we're using for our next play.

      Delete
    3. This sounds very cool. I'd like to start a small github project for special Android support with Xtend when the next release is out.
      The plan is to provide some easy to use wizards accompanied with examples and getting started material.
      Your ideas sound like they would be a useful addition also I need some more experienced Android devs to look at what I do .
      Are you interested in contributing?

      Delete
    4. I'd love to! I'd like to focus on things that are easily modifiable once created. Wizards can be a nice start (to avoid blank page syndrome) but they often generate a bunch of code that then has to be maintained. Some of the ADT Wizards are pretty nice right now, but leave the user with a good bit of code to maintain that they didn't write.

      I'm starting work on an Android book right now as well.

      Delete
    5. I agree absolutely. I mainly think about a wizard generating an example with some getting started documentation and an easy way to configure an Android project to work with Xtend, i.e. add the jars (with removed duplicate resources), set target folder to 'gen' and disable JSR-45 to allow debugging. It should be easy to get started but then the more useful stuff should be sustainable (i.e. not stupid wizards, but active annotations and the like).

      Delete
  15. Disable JSR-45? I added SMAP support in ANTLR 2 a good while back and it seemed to work fairly well in Eclipse to debug ANTLR grammars. (Speaking of which, I've gotta add that support for ANTLR 3/4), or set up a parser for the location comments that Ter adds to the generated code. I've been missing grammar-level debugging...

    What do you use in its place for debugging? Is there something better now? (Or did you mistype "enable"?)

    ReplyDelete
    Replies
    1. It doesn't work on Dalvik so you have to disable it for Android development.
      When disabled the compiler replaces the primary java source information in the bytecode with the corresponding Xtend source information.

      Delete