7.3
7.37.3 TargetTargetTargetTarget ---- ActionActionActionAction
Target – Action is easiest to see in the case of a Button control handled by a method of an activity. In the example (TargetAction in this chapter's folder), we have named the layout XML file “view.xml” and the Activity “Controller.java” for clarity. In view.xml, we have a Button and a TextView:
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".Controller"> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:layout_marginTop="112dp" android:text="@string/btn_text" android:onClick="action"/> <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:layout_marginTop="52dp" android:textAppearance= "?android:attr/textAppearanceMedium"/>
</RelativeLayout>
The onClick handler method for the button is named action, again, to clarify the role of each element in the Target – Action pattern. In Controller.java, an action method has been declared: public
public public
public voidvoidvoidvoidaction(View v) { /*
* The target is "this," the action is this.action * (this method)
*/
TextView tv = (TextView) findViewById(R.id.textView1); tv.setText("Hello, World!");
}
In the onCreate method of the Controller, the view for this activity has been set to view.xml: protected
protected protected
protected voidvoidvoidvoidonCreate(Bundle savedInstanceState) { super
supersupersuper.onCreate(savedInstanceState); setContentView(R.layout.view); }
The setContentView method specifies the view (the layout XML file) that this activity will handle events for. In other words, by specifying view.xml as the content view of the activity, the activity makes itself thetarget for UI events the occur on the view.
The view.xml file specifies that the “action” method (in its target) be executed in response to a touch event on its button, by setting the android:onClick attribute of the button to “action.” So the target is Controller.java (because Controller.java's content view is view.xml) and the action is the action method (because that is the onClick attribute of the button).
If we are declaring button handlers as methods of a controller, we can respond to more than one button's onClick event using a single handler. For example, if we were to add another button to the view (button2), we could rewrite our action method to switch among the buttons' ids and perform the correct action:
public public public
public voidvoidvoidvoidaction(View v) { /*
* The target is "this," the action is this.action * (this method)
*/
TextView tv = (TextView) findViewById(R.id.textView1); switch
switchswitchswitch(v.getId()) { case
casecasecase(R.id.button1) :
break breakbreakbreak; case
casecasecase(R.id.button2) : tv.setText("Bye!"); break
breakbreakbreak; default defaultdefaultdefault:
break breakbreakbreak; }
}
When we are dealing with onClick events of an essentially similar nature, we should adopt this practice.
Sometimes we declare an onClick listener as an instance of OnClickListener, and override the abstract method directly, as in this example:
@Override protected protected
protectedprotected voidvoidvoidvoidonCreate(Bundle savedInstanceState) { super
super
supersuper.onCreate(savedInstanceState); setContentView(R.layout.view);
Button btnBye = (Button) findViewById(R.id.button2); btnBye.setOnClickListener(newnewnewnewOnClickListener() {
@Override public public public
public voidvoidvoidvoidonClick(View v) {
//TODOTODOTODOTODOAuto-generated method stub
TextView tv = (TextView) findViewById(R.id.textView1); tv.setText("Bye!");
} });
}
What is important to realize is that this is still Target – Action. Here's the full listing of Controller.java, so you can compare both action methods:
package package package packagecom.ebook.targetaction; import import import importandroid.os.Bundle; import import import importandroid.app.Activity; import import import importandroid.view.View; import import import importandroid.view.View.OnClickListener; import import import importandroid.widget.Button; import import import importandroid.widget.TextView;
public public public
public classclassclassclassControllerextendsextendsextendsextendsActivity {
@Override protected protected
protectedprotected voidvoidvoidvoidonCreate(Bundle savedInstanceState) { super
super
supersuper.onCreate(savedInstanceState); setContentView(R.layout.view);
Button btnBye = (Button) findViewById(R.id.button2); btnBye.setOnClickListener(newnewnewnewOnClickListener() {
@Override public public public
public voidvoidvoidvoidonClick(View v) {
//TODOTODOTODOTODOAuto-generated method stub
TextView tv = (TextView) findViewById(R.id.textView1); tv.setText("Bye!"); } }); } public public
publicpublic voidvoidvoidvoidaction (View v) {
TextView tv = (TextView) findViewById(R.id.textView1); tv.setText("Hello, World!");
} }
The advantage of using methods of the controller to define actions is that it allows a single action method to respond to clicks on multiple UI elements in different ways. Any UI element may have an onClick attribute set to a method of its activity, as long as it is “clickable.” For elements that are not normally clickable, we can set android:clickable=”true” and then set the android:onClick attribute to the name of the appropriate click listener.
One advantage of using the OnClickListener class anonymously (as in the case of button2's listener, declared in onCreate), is that there is slightly less overhead. Another advantage is that we are able to change the behavior of a clickable item at runtime by changing its OnClickListener or even disabling it. But since the OnClickListener is dedicated to a specific UI element, it cannot listen for clicks from multiple buttons.
If we're handling onClick events, we can use either approach. But all other UI events must be handled by declaring an appropriate listener for the event. As said before, any View can be declared to have a listener; here are the event listeners for View objects:
• View.OnLongClickListener – a long touch event on a view
• View.OnFocusChangeListener – when a particular view gets or loses focus • View.OnKeyListener – a hardware key event when the view has focus • View.OnTouchListener – when the user performs any touch event
• View.OnCreateContextMenuListener – when a context menu is being built as a result of a sustained long click.
We will discuss all of many of these listeners in the next chapter. The important thing to keep in mind for now is that all UI event handling falls into the category of the Target – Action design pattern.
So Target – Action is simple: in fact it's is a constraint of the Android platform. If we use layout XML files and drive them from activities, there is literally no other way to respond to user events than to use Target – Action. But what about the case in which something happens in a class that should be responded to by another class? As an example, suppose our note application wishes to inform the user if they attempt to insert a duplicate row into the database.
One approach would be to have the model (the class that handles the data) try to execute a method of the controller directly by instantiating the controller and calling the method. But wait... the controller instantiates (and communicates directly with) the model, not the other way around. This approach would not work. What is needed is a way for the model to “tell the world” that something has happened. For this type of communication, we need a new design pattern:delegation.
7.4
7.4
7.47.4 DelegationDelegationDelegationDelegation
In its simplest form (in Android) delegation occurs when we use startActivityForResult with an intent and get the result back from the started activity using onActivityResult in the first activity. This form of delegation occurs between controllers (the activities involved). The key point here is that the second activity really has no knowledge of what the first activity is, nor does it care. It simply returns its result. The first activity can do whatever it wants (or nothing at all) with that result. The second activitydelegates its result and what to do with it to any
activity that may have started it.
Of course, there is no startModelForResult method, so this form of delegation will not work to communicate a runtime event from the model to the controller. For this, we will need to
implement an interface. The interface declares methods that will be performed by the delegate class (the controller) when they are called by the delegating class (the model). For
example, we might write an interface containing a method duplicateRowEntered: public
public public
public interfaceinterfaceinterfaceinterfaceDatabaseEvents { public
publicpublicpublic abstractabstractabstractabstract voidvoidvoidvoidduplicateRowEntered(); }
Then implement the interface in our main activity (the controller): public
public public
public classclassclassclassControllerextendsextendsextendsextendsActivityimplementsimplementsimplementsimplementsDatabaseEvents { //...
}
When we declare that a class implements an interface, we are saying that the class will implement the abstract methods of that interface. In other words, by declaring that Controller implements DatabaseEvents, we are promising to implement the duplicateRowEntered method. Here is a simple implementation in which we log out the fact that the method was executed:
public public public
public voidvoidvoidvoidduplicateRowEntered() { Log.i("delegate","DUP!"); }
In the Model class, we call the method on the controller: public
publicpublicpublic voidvoidvoidvoiddelegate() {
Controller c =newnewnewnewController(); c.duplicateRowEntered(); c =nullnullnullnull;
}
Note that we must instantiate the Controller in order to get to the method, but we immediately set the Controller object to null afterward. In a realistic example, we would attempt to make the insertion only if the row were unique. If it were not, we would fire off the delegating class' implementation of the duplicateRowEntered method.
In delegation using interfaces in Java, we must know to what class (or classes) we are delegating to. This is usually not a problem within our own apps since we will nearly always know the identity of the class(es) that should implement the interface methods. In cases
where this information cannot be known until runtime, we have other techniques to rely on, such as broadcasting an intent or simply returning an error code from a method (as in the case of our duplicate insertion check). Delegation using interfaces is a tool that we can use in cases where we know the identity of the class that should perform the delegated method. In some other languages, we can delegate “to the world.” In these languages, the identity of the delegate class is never known until runtime. Objective – C is an example of such a language.
A simple example of delegation (in which the controller calls a method of the delegate class which calls a delegate back on the controller object) is included in the TargetAction project for your perusal.
Summary Summary
SummarySummary ofofofof CommunicationCommunicationCommunicationCommunication inininin MVCMVCMVCMVC
The cardinal rule of MVC is to limit communication between the classes and components that make up an application. Controllers instantiate the Models and send messages (by calling methods) to them. In Android, Controllers are bound to a specific view by using the setContentView method. The messages that a controller sends to its view may alter the contents of the view or any of its subviews in some way.
Messages from the View to the Controller are carried using Target – Action. The Controller is the target, and the action is the method (or event listener) that responds to an event on the view or on one of its controls. Delegation through intents (using startActivityForResult and onActivityResult) may pass information from one view / controller pair back to the controller that started that activity.
Communication from the Model to the Controller can be conducted in basically three ways (with variations):
• Using delegation through an interfaces (in cases where the controller's identity is known at compile time)
• Via the return values of called methods
• By broadcasting an intent for which the controller implements a broadcast receiver.