Saturday 24 September 2011

Responding to clicks

When writing an app you would think that something like responding to a click on a button would be straightforward and in many ways it is. Working on my own, with no one to ask, I am drawn to examples and they often use a style of code that I find messy and hard to follow. That means, of course, that it will be hard to maintain and may hide nasty bugs, so it is a very bad idea. One of the things I find annoying is the amount of code lying around in the onCreate section of the class, but much of this code is not run when the activity is created, but when some event takes place. This seems wrong to me, so I wanted to find a way to avoid this in my code.

To deal with a click of a button you have to provide an onClickListener function and link that function to your button. You might have many buttons on an activity. Some may share a function or have one each. I have discovered four ways of doing this.

First, lets look at the often used way. In the onCreate routine for the activity this is added:

        final Button button1 = (Button) findViewById(R.id.cmd1);
        button1.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                Context context = v.getContext();
                String txtid = Integer.toString(v.getId());
                CharSequence text = "Button 1 " + txtid;
                int duration = Toast.LENGTH_SHORT;

                Toast toast = Toast.makeText(context, text, duration);
                toast.show();
            }
        });

The main advantage of this method seems to be that all of the code is in one place, the function itself and the code to enable the function for a specific button is all there together.  This also seems like a disadvantage to me too. A (potentially large) chunk of code is stuck in the onCreate function of the activity, rather than a simple statement to point to function in a separate part of the source file. Notice how there is a horrible "});" stuck at the end to show the nasty embedded nature of the declaration. Most of this code does not get run when the activity is created, but when a button is clicked, so why is it in the onCreate section?

The second example is in two parts, the first is in the onCreate of the activity which finds the button and points to the onClickListener routine

        final Button button2 = (Button) findViewById(R.id.cmd2);
        button2.setOnClickListener(cmd2_clicklistener);



The second part is the listener routine itself. This can be anywhere else in the source file, probably grouped with other listener routines.

    private OnClickListener cmd2_clicklistener = new OnClickListener() {
        public void onClick(View v) {
            Context context = v.getContext(); //getApplicationContext();
            String txtid = Integer.toString(v.getId());
            CharSequence text = "Button 2 " + txtid;
            int duration = Toast.LENGTH_SHORT;

            Toast toast = Toast.makeText(context, text, duration);
            toast.show();
        }
    };



The main effect of this is to separate the listener routine from the onCreate area. It does still have a complication that there is a function declared within a function, but it is easier to read. many of these can be created separately, but also more than one button could share one listener, with the code checking which button called it if needed. Using these declarations that are separate from the allocation of the listener makes it possible to change the listener that responds to a button during the run time.

There is yet another way to deal with onClickListeners. So far we have told the activity what function to call during the run time. Another way is to define the function name at design time. To do this in the button definition xml in the layout add    

android:onClick="clickfunc".  

This will cause a call to the function named which should have the prototype

  public void clickfunc(View view). 

This seems very simple yet having defined this the listener can still be overridden by a setOnClickListener call later just like the second example. There is no clutter in the onCreate section at all. It does, however, make the layout and code less independent. The listener in my example is this


    public void cmd3_click(View v) {
        Context context = v.getContext(); //getApplicationContext();
        String txtid = Integer.toString(v.getId());
        CharSequence text = "Button 3 " + txtid;
        int duration = Toast.LENGTH_SHORT;

        Toast toast = Toast.makeText(context, text, duration);
        toast.show();
    }



The final way I have found to respond to clicks is not very flexible, but if your activity has a single button it works well, and still removes the clutter in the onCreate section. You can make your class contain an onClick function and set the listener to this. To do this you must add the extention implements onClickListener to the definition of the activity class. You can then add a handler for onClick to the class (outside of the onCreate section of course) like this


    public void onClick(View v) {
        Context context = v.getContext();
        String txtid = Integer.toString(v.getId());
        CharSequence text = "Button 4 " + txtid;
        int duration = Toast.LENGTH_SHORT;

        Toast toast = Toast.makeText(context, text, duration);
        toast.show();
    }



The declaration of how to use the handler goes in the onCreate section as before, but now it simply points to the class itself (this)


        final Button button4 = (Button) findViewById(R.id.cmd4);
        button4.setOnClickListener(this);



I am pleased to have found alternatives to stuffing code into the onCreate section. Now I need to do the same for other listeners and handlers to make things much more readable.