Objectives

This is a short lab whose aim is to provide a detailed explanation of event handling via a simple standalone non-android application

ResidenceActivity

Here is an extract from ResidenceActivity.java where the TextWatcher listener is introduced by means of an anonymous class:

  EditText geolocation;
  ...
  ...
  ...
  private void geolocation(View v)
  {
    // Respond to user input
    geolocation = (EditText) v.findViewById(R.id.geolocation);
    geolocation.addTextChangedListener(new TextWatcher()
    {
      public void onTextChanged(CharSequence c, int start, int before, int count)
      {
        residence.setGeolocation(c.toString());
      }

      public void beforeTextChanged(CharSequence c, int start, int count, int after)
      {
        // this space intentionally left blank
      }

      public void afterTextChanged(Editable c)
      {
        // this space intentionally left blank
      }
    });
  }

Figure 1 provides an outline analysis of the method.

Figure 1: Analysis of callback and listener code

In the following steps we shall develop short programs that will, hopefully, explain more fully how the pattern in this method, geolocation, works.

We shall select names for classes, interface and methods that, where appropriate, are the same as in geolocation.

Callback (Standalone method)

This approach is for the purpose of instruction only and is unlikely to be used very much in practice.

  • Create a project in Eclipse named callbacks.
  • Within the project create a package org.wit.callback.
  • We shall populate this package with the following files:
    • Callback.java
    • EventLoop.java
    • TextView.java
    • TextWatcher.java

Here is the TextWatcher code:

Filename: TextWatcher.java

package org.wit.callback;

public interface TextWatcher
{
  void onTextChanged(String changedtext);
}

As you can see, TextWatcher is a simple interface with one method.

  • Both the interface name and its method have been chosen to correspond to names in the ResidenceActivity code (see Figure 1 in previous step).

Next, create a class that implements TextWatcher:

Filename: Callback.java

package org.wit.callback;

public class Callback implements TextWatcher
{
    @Override
    public void onTextChanged(String changedtext)
    {
      System.out.println(changedtext);
    } 
}

This class does nothing other than implement the TextWatcher method onTextChanged

  • Compare again with the code in ResidenceActivity.

The next class we develop is TextView:

Filename: TextView.java


package org.wit.callback;

public class TextView
{
  private TextWatcher textwatcher;
  private boolean somethingHappened;

  public void addTextChangedListener(TextWatcher textwatcher)
  {
    // Save the event object for later use.
    this.textwatcher = textwatcher;
    // Nothing to report yet.
    somethingHappened = false;
  }

  // Invoking with flag == true sets scene for a callback
  public void setPredicate(boolean flag)
  {
    somethingHappened = flag;
  }

  // This method will be invoked repeatedly in an event loop
  public void doWork()
  {
    // Check the predicate, which is set elsewhere.
    if (somethingHappened)
    {
      // Signal the event by invoking the interface's method.
      textwatcher.onTextChanged("Finally - you called back");
      somethingHappened = false;
    }
  }
}

Here is a brief analysis of TextView:

  • It contains a reference named textwatcher to an object whose class implements TextWatcher interface.
  • A boolean somethingHappened, when possessing the value true, indicates that an event has occurred.
  • The method addChangedListener:
    • Takes as a parameter a reference to an object whose class implements the TextWatcher interface
    • Sets somethingHapped to false indicating that an event has yet to occur.
  • The public method setPredicate exists to change the state of somethingHappened, thus indicating an event has occurred.
  • The method doWork:
    • Does nothing if somethingHappened is false
    • Invokes onTextChanged on textWatcher if somethingHappened is true.
      • This is the callback: textWatcher is a reference to an object that was passed in as an argument in addTextChangedListener. We shall see where this invocation occurred in the next and final class in the project.

Filename: EventLoop.java

package org.wit.callback;

// Class to simulate a short-lived event loop
public class EventLoop
{
  public static void main(String[] args)
  {
    TextWatcher textwatcher = new Callback();
    TextView textview = new TextView();
    textview.addTextChangedListener(textwatcher);

    int val = 0;
    // The simulated event loop
    do
    {
      if (val % 100 == 0)
      {
        textview.setPredicate(true); // the trigger to fire an event
      }
      // invoke repeatedly but trigger event only when predicate true
      textview.doWork();
      val += 1;
    } while (val < 500);// we expect 5 events to be triggered
  }
}

This class simulates an event loop.

  • It models a situation where the system is waiting for text input to an edit widget (control).
  • When text is entered an event is triggered and the interface method onTextChanged invoked.

In more detail:

  • We instantiate a Callback object and assign it to a variable textwatcher of interface type TextWatcher
  • Next we instantiate TextView and invoke addTextChangedListener on the object created passing textwatcher as a parameter
  • We then set up the event loop and at specific times set the predicate to true which causes an event to be triggered.

Run the application and observe the output:

Figure 1: Callback in action

Callback (Anonymous class method)

We shall now dispense with the Callback class

  • Create a new package in the callbacks project named org.wit.callbackanon.
  • Copy the file EventLoop.java from org.wit.callback to the new package.
  • A number of errors will be indicated which may be resolved by adding the following import statements:
import org.wit.callback.TextView;
import org.wit.callback.TextWatcher;

Study the original EventLoop code:

Note the content of Callback:

Open org.wit.callbackanon.EventLoop.java and:

  • Delete the line that instantiates a new Callback object: TextWatcher textwatcher = new Callback();
    • We no longer require Callback
  • Replace the line: textview.addTextChangedListener(textwatcher); with this code:
    textview.addTextChangedListener(new TextWatcher()
    {
      @Override
      public void onTextChanged(String changedtext)
      {
        System.out.println(changedtext);
      }

    });

It should be clear what's happening: we have dispensed with the class Callback and instead used its content within what is referred to as an anonymous class as a parameter to addTextChangedListener..

  • Official documentation on the anonymous class syntax is available in the Java Tutorial.

Here is the final refactored EventLoop code:

Filename: EventLoop.java

package org.wit.callbackanon;

import org.wit.callback.TextView;
import org.wit.callback.TextWatcher;

//Class to simulate a short-lived event loop
public class EventLoop
{

  public static void main(String[] args)
  {

    TextView textview = new TextView();

    // We use an anonymous class instead of the Callback object
    textview.addTextChangedListener(new TextWatcher()
    {
      @Override
      public void onTextChanged(String changedtext)
      {
        System.out.println(changedtext);
      }

    });

    // The simulated event loop
    int val = 0;
    do
    {
      if (val % 100 == 0)
      {
        textview.setPredicate(true); // the trigger to fire an event
      }
      textview.doWork();// invoked repeatedly and triggers event when predicate
                        // true
      val += 1;
    } while (val < 500);// we expect 5 events to be triggered
  }
}

Figure 1 below presents a flow diagram of the program.

Figure 1: Flow diagram of callback program

Callback (Delegated method)

  • Create a package in callbacks project named org.wit.callbackimpl.
  • Copy the file EventLoop.java from org.wit.callbackanon to the new package.

  • Reorganize EventLoop

    • by moving its functionality to a method:
      • public void runloop()
    • Instantiating EventLoop within the main method
    • Invoking runloop on the newly creaed Eventloop object.

Here is the refactored class:

org.wit.callbackimpl.EventLoop.java

package org.wit.callbackimpl;

import org.wit.callback.TextView;
import org.wit.callback.TextWatcher;

//Class to simulate a short-lived event loop
public class EventLoop
{

  private void runloop()
  {
    TextView textview = new TextView();

    // We use an anonymous class instead of the Callback object
    textview.addTextChangedListener(new TextWatcher()
    {
      @Override
      public void onTextChanged(String changedtext)
      {
        System.out.println(changedtext);
      }

    });

    // The simulated event loop
    int val = 0;
    do
    {
      if (val % 100 == 0)
      {
        textview.setPredicate(true); // the trigger to fire an event
      }
      textview.doWork();// invoked repeatedly and triggers event when predicate
                        // true
      val += 1;
    } while (val < 500);// we expect 5 events to be triggered    
  }


  public static void main(String[] args)
  {
    EventLoop obj = new EventLoop();
    obj.runloop();
  }    
}
  • Test that the result of this change has no noticeable effect on the output:

  • As shown in Figure 1, replace the anonymous class with the this reference.

    • Remember that here this is a reference to the instance of EventLoop that has been created.

Figure 1

  • This change will trigger an error:

  • The reason for the error is that the signature of addTextChangedListener clearly shows that a TextWatcher type is expected as a parameter whereas we have supplied it with an EventLoop reference.

  • We can eliminate the error by:

    • Having EventLoop implement TextWatcher
      • Doing so means that this, a reference to EventLoop, becomes an acceptable parameter
    • We are then obliged to implement the method declared in TextWatcher, namely onTextChanged.

Change the signature of EventLoop to the following:

public class EventLoop implements TextWatcher

Use QuickFix to override onTextChanged in the class:

  @Override
  public void onTextChanged(String changedtext)
  {
    // TODO Auto-generated method stub

  }

Finally, fully implement onTextChanged:

      @Override
      public void onTextChanged(String changedtext)
      {
        System.out.println(changedtext);
      }

The application should now be error-free.

  • Run it and observe the result as before:

Here is the final version of EventLoop:

package org.wit.callbackimpl;

import org.wit.callback.TextView;
import org.wit.callback.TextWatcher;

//Class to simulate a short-lived event loop
public class EventLoop implements TextWatcher
{

  public void runloop()
  {    
    TextView textview = new TextView();

    // EventLoop implements TextWatcher
    // Consequently "this" a legal parameter here
    textview.addTextChangedListener(this);

    // The simulated event loop
    int val = 0;
    do
    {
      if (val % 100 == 0)
      {
        textview.setPredicate(true); // the trigger to fire an event
      }
      textview.doWork();// invoked repeatedly, triggers event when predicate true
      val += 1;
    } while (val < 500);// we expect 5 events to be triggered 
  }
  public static void main(String[] args)
  {
    EventLoop obj = new EventLoop();
    obj.runloop();

  }

  @Override
  public void onTextChanged(String changedtext)
  {
    System.out.println(changedtext);

  }
}

Exercise 1

Modify the the Delegate callback method as follows:

  • Introduce a new interface, KeyBoardListener, inherited from the existing interface TextWatcher.
    • The interface to have one method: void onKeyBoardInput()
  • Introduce a new class called KeyPress, a subclass of TextView
    • In Keypress, override the TextView method doWork as indicated below.
  • Modify EventLoop as follows:
    • The class should implement the new interface only
    • Rather than TextView textview being an instance of TextView, have it be an instance of the derived class Keypress.
    • In the simulated event loop capture a character from a keyboard press.
      • If the character is "c":
        • Set the predicate true.
        • Implement the necessary interface method(s).
      • If the character is "q", quit the program.
    • Here is sample output:
      • EventLoop is provided in skeleton form below.

Here is the package and file arrangement you are recommended to use:

  • Create a new package org.wit.callbackexercise.
    • Populate this package with
      • Two new classes:
        • EventLoop
        • Keypress
      • One new interface
        • KeyBoardListener

Here is skeleton code for EventLoop class with hints

package org.wit.callbackexercise;

import java.util.Scanner;

import org.wit.callback.TextView;

//Class to simulate a short-lived event loop
public class EventLoop 
{
  String keyboardInput;
  static Scanner in = new Scanner(System.in);

  public void runloop()
  {
    TextView textview = new Keypress();
    // EventLoop implements KeyBoardListener
    // Consequently "this" a legal parameter here
    textview.addTextChangedListener(...);
    // The simulated event loop
    do
    {
      keyboardInput = keyboard();
      if (keyboardInput.equals("c"))
      {
        textview.setPredicate(true); // the trigger to fire an event
      }
      textview.doWork();//if predicate true then trigger event in doWork
    } while (keyboardInput.equals("q") == false);
    System.out.println("Thanks for your time - bye");
  }

  /*
   * Capture and return a single keyboard character
   */
  public String keyboard()
  {
    String s = "";
    if(in.hasNext())
    {
      s = in.next();
    }
    return s;
  }

  public static void main(String[] args)
  {
    EventLoop obj = new EventLoop();
    obj.runloop();
    in.close();
  }
}

Here is a skeleton of the derived Keypress class:

package org.wit.callbackexercise;

import org.wit.callback.TextView;

public class Keypress extends TextView
{
  public void addKeyBoardListener(KeyBoardListener listener)
  {
    // Save the event object for later use.
    //TODO ...
  }

  // This method will be invoked repeatedly in an event loop
  @Override
  public void doWork()
  {
    // Check the predicate, which is set elsewhere.
    if (somethingHappened)
    {
      // Signal the event by invoking the interface's method.
      //TODO: Invoke: onKeyBoardInput();
      //TODO: Invoke: onTextChanged("Finally - you called back");
      somethingHappened = false;
    }
  }
}

Here are suggested class diagrams for the solution.

Archives

This is downloadable archive of the event handling code complete to the end if this lab: