Android event handling & inline interface implementation
with Oxygene for Java

Brian Long Consultancy & Training Services Ltd.
January 2012

Oxygene for Java

Introduction

Java Android

This article looks at how event handlers work in the Java world that Android inhabits, how it differs from the Delphi and .NET way that many Oxygene for Java users will be more familiar with, and how the new inline interface implementation feature of Oxygene 5 helps work with Java-style events.

If you are new to Oxygene for Java, follow this link to an introductory article on using it to create Android applications.

The accompanying source project is available through this download link.

Java events vs. Delphi/.NET events

In Delphi we are familiar with events being special extensions to method pointers, defined as procedural types using the of object suffix, i.e. closures. When you define a data field using one of these types and then (typically) expose it from the class using a property definition, you're all set. Any object instance's method with a suitable signature can be assigned to this Delphi single-casting event field/property.

In .NET things are not that different. At the implementation level you're creating a descendant of a System.MulticastDelegate object, but in code you just add in the delegate keyword to what is essentially a procedural type definition and you get the required effect. Ignoring the specifics of how you add and remove handlers to a .NET multicast delegate we are essentially dealing, as in Delphi, with a field defined in terms of a procedural type definition.

In Java things are rather different. The standard procedure in the Java world is to define events in terms of interfaces, typically interfaces with a single method declared in them. This interface method dictates the signature of a suitable event handler, but of course you then need to have a class at your disposal that implements the interface and which you can assign to an event handler data field, whose type will be the interface type.

This is a bit of a switch around from what we may be familiar with in the world of Delphi and .NET. In those environments you just need an object (any object, including a form or whatever) to define a method with a matching signature and you can then add a reference to the method (from an appropriate instance of the object) to the event field/property.

The following sections show various options available to the Oxygene for Java programmer for handling an event defined using an interface type. Hopefully this will help smooth the transition for Delphi programmers looking at writing Android applications in Oxygene for Java.

The scenario covered by each option is a Button widget displayed by the Activity (aka screen) whose click event we want to handle. A click event for any view is defined in terms of the View.OnClickListener interface, which has a single method, onClick, taking a View parameter. It is usually set up with the view's setOnClickListener() method (or optionally, in Oxygene, the OnClickListener write-only implied property).

Option 1: Implementing the event interface in the Activity

Bearing in mind that an event is defined in terms of an interface type, the first option we'll consider is where we implement the View.OnClickListener event interface in the button's activity class, as shown here:

  1. type
  2.   MainActivity = public class(Activity, View.OnClickListener)
  3.   private
  4.     method ShowMsg(msg: String);
  5.   public
  6.     method onCreate(savedInstanceState: Bundle); override;
  7.     method onClick(v: View);
  8.   end;
  9.  
  10. implementation
  11.  
  12. method MainActivity.onCreate(savedInstanceState: Bundle);
  13. begin
  14.   inherited;
  15.  
  16.   //Set our view from the "main" layout resource
  17.   ContentView := R.layout.main;
  18.  
  19.   //To respond to button clicks, you need to call the button's setOnClickListener
  20.   //method, passing an object that implements the interface View.OnClickListener.
  21.   //In Oxygene for Java it is more convenient and common to assign a similar value
  22.   //to the button's OnClickListener implied property
  23.  
  24.   //Example 1:
  25.   var button1: Button := Button(findViewById(R.id.button1));
  26.   button1.OnClickListener := self; 
  27. end;
  28.  
  29. method MainActivity.ShowMsg(msg: String);
  30. begin
  31.   Toast.makeText(self, msg, Toast.LENGTH_SHORT).show
  32. end;
  33.  
  34. method MainActivity.onClick(v: View);
  35. begin
  36.   ShowMsg('You pressed Button 1')
  37. end; 

The handler is set up by assigning the activity object, self, to OnClickListener.

While this approach works perfectly fine, there's a glaring shortcoming with it. Having implemented the interface method once for this one button that's about all we can do. If we want respond to the user clicking any other button or any other control in this activity we can't use the same approach for them as we've already implemented the click listener interface and given its method some functionality. So while it works as a one-off, it's not really that useful in the general case, at least not for such a general purpose event as the click event. There might be an argument for its use in the case of a very specific event of a widget of which there is a single instance. And certainly in the general non-event case of needing to implement an interface it's all fine, but for event handling, it's really of limited use.

Of course you could also declare additional class types in order to implement the interface more times for other handlers but that quickly becomes unwieldy. So, ignoring that possibility, what's next?

Option 2: Inline interface implementation with an existing method

Now we come to this new Oxygene syntax, added to the language to allow Oxygene programmers to work with these interface-based events in much the same way as Java programmers do. The idea is to have the compiler generate an anonymous class that implements the event interface. Then for each method defined in the interface you can (optionally) provide a method with a suitable signature that will be used as the implementation of the interface's method.

Let's have a look at how the syntax of inline interface implementation is laid out:

  1. type
  2.   MainActivity = public class(Activity)
  3.   private
  4.     method ShowMsg(msg: String);
  5.   public
  6.     method onCreate(savedInstanceState: Bundle); override;
  7.     method button2OnClick(v: View);
  8.   end;
  9.  
  10. implementation
  11.  
  12. method MainActivity.onCreate(savedInstanceState: Bundle);
  13. begin
  14.   inherited;
  15.  
  16.   //Set our view from the "main" layout resource
  17.   ContentView := R.layout.main;
  18.  
  19.   //Example 2:
  20.   //We can use an inline interface implementation. This creates an anonymous helper
  21.   //class that implements the specified interface and allows us to assign implementations
  22.   //to as many of the interface methods as we are interested in. The rest will get empty
  23.   //stub implementations. For View.OnClickListener there is only one method.
  24.   //In this case we make use of a method in the activity to act as the onClick
  25.   //implementation
  26.   var button2: Button := Button(findViewById(R.id.button2));
  27.   button2.OnClickListener := new interface View.OnClickListener(
  28.     onClick := @button2OnClick);
  29. end;
  30.  
  31. method MainActivity.ShowMsg(msg: String);
  32. begin
  33.   Toast.makeText(self, msg, Toast.LENGTH_SHORT).show
  34. end;
  35.  
  36. method MainActivity.button2OnClick(v: View);
  37. begin
  38.   ShowMsg('You pressed Button 2')
  39. end;
  40.  
  41. end. 

You can see the new interface construct being used to get the compiler to create an anonymous object that implements the View.OnClickListener interface, and then the bracketed section sets up the interface methods. Given that an interface can define multiple methods we have a comma-separated list here (if necessary - not in this case) that associates a method of the interface with a method to implement it with, which of course requires the appropriate matching signature. In the case of passing an existing method, in this case one in this activity class, we require an @ prefix. For any interface methods that we are not interested implementing we can just ignore them; the compiler will supply an empty stub method. In this case that latter point is irrelevant as the interface in question only has one method, onClick.

Option 3: Abbreviated inline interface implementation

This next possible approach is exactly the same as Option 2 above, just slightly abbreviated. In cases where the interface does have just a single method we are permitted a small abbreviation, leaving an assignment which is quite reminiscent of an old Delphi event property assignment.

  1. type
  2.   MainActivity = public class(Activity)
  3.   private
  4.     method ShowMsg(msg: String);
  5.   public
  6.     method onCreate(savedInstanceState: Bundle); override;
  7.     method button3OnClick(v: View);
  8.   end;
  9.  
  10. implementation
  11.  
  12. method MainActivity.onCreate(savedInstanceState: Bundle);
  13. begin
  14.   inherited;
  15.  
  16.   //Set our view from the "main" layout resource
  17.   ContentView := R.layout.main;
  18.  
  19.   //Example 3:
  20.   //Because View.OnClickListener only has one method we can simplify this
  21.   //using special shorthand to make it look more 'normal' to Delphi eyes
  22.   var button3: Button := Button(findViewById(R.id.button3));
  23.   button3.OnClickListener := @button3OnClick;
  24. end;
  25.  
  26. method MainActivity.ShowMsg(msg: String);
  27. begin
  28.   Toast.makeText(self, msg, Toast.LENGTH_SHORT).show
  29. end;
  30.  
  31. method MainActivity.onClick(v: View);
  32. begin
  33.   ShowMsg('You pressed Button 1')
  34. end;
  35.  
  36. method MainActivity.button3OnClick(v: View);
  37. begin
  38.   ShowMsg('You pressed Button 3')
  39. end;
  40.  
  41. end. 

Again, this is specific to interfaces defined to contain just a single method.

Option 4: Inline interface implementation with an anonymous method

The next possibility is to use a variant on Option 2 above. Instead of using an existing method with the required signature, we can define an anonymous method in situ:

  1. type
  2.   MainActivity = public class(Activity)
  3.   private
  4.     method ShowMsg(msg: String);
  5.   public
  6.     method onCreate(savedInstanceState: Bundle); override;
  7.   end;
  8.  
  9. implementation
  10.  
  11. method MainActivity.onCreate(savedInstanceState: Bundle);
  12. begin
  13.   inherited;
  14.  
  15.   //Set our view from the "main" layout resource
  16.   ContentView := R.layout.main;
  17.  
  18.   //Example 4:
  19.   //Instead of a regular method we can use an anonymous method,
  20.   //which gains access to any local variables in this method we are in
  21.   var button4: Button := Button(findViewById(R.id.button4));
  22.   button4.OnClickListener := new interface View.OnClickListener(
  23.     onClick := method(v: View)
  24.     begin
  25.       ShowMsg('You pressed Button 4')
  26.     end);      
  27. end;
  28.  
  29. method MainActivity.ShowMsg(msg: String);
  30. begin
  31.   Toast.makeText(self, msg, Toast.LENGTH_SHORT).show
  32. end;
  33.  
  34. end. 

This is useful if the code is quite brief and is only needed for this particular use. It does, however, offer the benefit that it has access to the locals of the subroutine it is implemented in. So if we had useful local variables defined in onCreate() then the anonymous method's implementation could access them.

Option 5: Inline interface implementation with a lambda expression

Rather similar to Option 4 above is the option to use a lambda expression instead of anonymous method.

  1. type
  2.   MainActivity = public class(Activity)
  3.   private
  4.     method ShowMsg(msg: String);
  5.   public
  6.     method onCreate(savedInstanceState: Bundle); override;
  7.   end;
  8.  
  9. implementation
  10.  
  11. method MainActivity.onCreate(savedInstanceState: Bundle);
  12. begin
  13.   inherited;
  14.  
  15.   //Set our view from the "main" layout resource
  16.   ContentView := R.layout.main;
  17.  
  18.   //Example 5:
  19.   //We can also use a lambda expression to achieve the same end
  20.   var button5: Button := Button(findViewById(R.id.button5));
  21.   button5.OnClickListener := new interface View.OnClickListener(
  22.     onClick := -> ShowMsg('You pressed Button 5'));
  23. end;
  24.  
  25. method MainActivity.ShowMsg(msg: String);
  26. begin
  27.   Toast.makeText(self, msg, Toast.LENGTH_SHORT).show
  28. end;
  29.  
  30. end. 

In this scenario a lambda expression is basically a shortcut for writing an anonymous method and so again, the body of the lambda has access to any locals defined in the enclosing method.

Option 6: Use a layout file attribute

All the previous examples show different ways to set up an event handler for (in this example's case) a button click event. This final option uses no Oxygene code at all to set up the handler and so doesn't even need a variable to represent the button:

  1. type
  2.   MainActivity = public class(Activity)
  3.   private
  4.     method ShowMsg(msg: String);
  5.   public
  6.     method onCreate(savedInstanceState: Bundle); override;
  7.     method button6OnClick(v: View);
  8.   end;
  9.  
  10. implementation
  11.  
  12. method MainActivity.onCreate(savedInstanceState: Bundle);
  13. begin
  14.   inherited;
  15.  
  16.   //Set our view from the "main" layout resource
  17.   ContentView := R.layout.main;
  18.  
  19.   //Example 6:
  20.   //In the case of onClick, this can be set up in the XML layout
  21.   //meaning no setup is required here at all
  22.   //var button6: Button := Button(findViewById(R.id.button6));
  23. end;
  24.  
  25. method MainActivity.ShowMsg(msg: String);
  26. begin
  27.   Toast.makeText(self, msg, Toast.LENGTH_SHORT).show
  28. end;
  29.  
  30. method MainActivity.button6OnClick(v: View);
  31. begin
  32.   ShowMsg('You pressed Button 6')
  33. end;
  34.  
  35. end. 

This code-less approach is achieved by setting up the event handler iun the activity's layout file:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout
  3.  xmlns:android="http://schemas.android.com/apk/res/android"
  4.  android:orientation="vertical"
  5.  android:layout_width="fill_parent"
  6.  android:layout_height="fill_parent"
  7.    >
  8.  
  9.   <ScrollView
  10.    android:layout_width="fill_parent"
  11.    android:layout_height="fill_parent" >
  12.  
  13.     <LinearLayout
  14.      android:orientation="vertical"
  15.      android:layout_width="fill_parent"
  16.      android:layout_height="wrap_content"
  17.      android:layout_gravity="center_horizontal"
  18.      >
  19.  
  20.       <Button
  21.        android:id="@+id/button6"
  22.        android:text="Button 6"
  23.        android:layout_width="fill_parent"
  24.        android:layout_height="wrap_content"
  25.        android:onClick="button6OnClick" />
  26.  
  27.     </LinearLayout>
  28.  
  29.   </ScrollView>
  30.  
  31. </LinearLayout>

Buttons can have an onClick attribute in the XML layout to identify the click event handler method in the activity class their layout gets loaded into. Other views can also use this onClick attribute, however whether they respond to clicks by calling the specified handler or not is dependant on the value of their clickable attribute (or whether setClickable(true) or Clickable := true has been executed). The Button class sets clickable to true.

Clicking a button in the sample app that accompanies this article

Summary

Event handling is different in the Java world to what we might be familiar with from experience in the Delphi or .NET world - events are managed through interface references. Oxygene for Java takes account of this alternate approach and offers up a new syntax and a variety of options to flexibly work with events in the appropriate Java-esque way. Here we've looked at a handful of possibilities to ensure you are up to date on what options are available and so that sample code is more digestible to you when you encounter it.

Oxygene

Comments

If you wish to make any comments on this article, your best options are to comment on the blog post that announced it, use the Contact Me form or email me at .


About the author

Brian Long has spent the last 1.6 decades as a trainer, trouble-shooter and mentor focusing on the Delphi, Oxygene, C# and C++ languages, and the Win32, .NET and Mono platforms, recently adding iOS and Android onto the list. In his spare time, when not exploring the Chiltern Hills on his mountain-bike or pounding the pavement in his running shoes, Brian has been re-discovering and re-enjoying the idiosyncrasies and peccadilloes of Unix-based operating systems. Besides writing a Pascal problem-solving book in the mid-90s he has contributed chapters to several books, written countless magazine articles, spoken at many international developer conferences and acted as occasional Technical Editor for Sybex. Brian has a number of online articles that can be found at http://blong.com and a blog at http://blog.blong.com.

© 2012 Brian Long Consulting and Training Services Ltd. All Rights Reserved.


Go back to the top of this page