48

Suppose we have an Activity with a lot of views on which OnClickListener is to be registered.

The most common way to implement this is to let the Activity-Subclass implement the OnClickListener, something like this:

public class ActivityMain extends Activity implements View.OnClickListener
{   
    @Override
    public void onClick(View view)
    {
        switch (view.getId())
        {
            //handle multiple view click events
        }
    }
}

The way I like to implement it is to create a private class inside the Activity-Subclass and let that inner class implement the OnClickListener:

public class ActivityMain extends Activity implements View.OnClickListener
{
    private class ClickListener implements View.OnClickListener
    {   
        @Override
        public void onClick(View view)
        {
            switch (view.getId())
            {
                //handle multiple view click events
            }
        }
    }
}

This way the code seems more organized and easy to maintain.

Moreover, talking about "Is-a", "Has-a" relationships, the latter seems to be a good practice because now the Activity-Subclass would have a "Has-a" relationship with the ClickListener. While in the former method we would be saying that Our Activity-Subclass "Is-a" ClickListener, which ain't completely true.

Note that, I am not concerned with the memory overhead the latter would cause.

Also, adding onClick tag in xml is completely out of question.

So, what really is the best way to implement a ClickListener?

Please don't suggest any libraries like RoboGuice or ButterKnife etc.

UPDATE:

I would like to share the approach I finally adopted.

I directly implement the listener in Activity/Fragment.

As far as OOP design is concerned. The "HAS-A" approach doesn't offers any practical benefits and even takes up more memory. Considering the amount of nested classes (and the memory overhead) we will be creating for every similar listener we implement, this approach should clearly be avoided.

Ola Ström
  • 4,136
  • 5
  • 22
  • 41
Sarthak Mittal
  • 5,794
  • 2
  • 24
  • 44
  • 1
    Your question is very subjective, what do you mean by "best". What is your goal/aim? – cyroxis May 06 '15 at 17:05
  • 1
    aim is simple, follow best practices :) – Sarthak Mittal May 06 '15 at 17:08
  • 1
    Your `ClickListener` is an inner non-static class the coupling of this 'has-a' is no different than if your class `Activity` implemented `View.OnClickListener`. This is because your inner `ClickListener` requires an instance of `ActivityMain` and really can't be reused. I would argue that you're over engineering and aren't actually gaining anything. – Charles Durham May 06 '15 at 17:21
  • @CharlesDurham this particular case is not about code reuse, its more about better OO and easy maintenance... – Sarthak Mittal May 06 '15 at 17:25
  • 3
    Saying 'has-a' is better than 'is-a' is not important. What's important is WHY software developers prefer 'has-a' relationships to 'is-a'. In this case your abstraction doesn't gain any of the usual benefits from using composition instead of inheritance. – Charles Durham May 06 '15 at 17:27
  • @CharlesDurham but what i believe is that my abstraction improves maintenance of the code if not an arguably better practice, when the Activity gets extremely complex with lots of callbacks implemented directly, the code is a lot harder to maintain, better practice would be to let an inner class implement similar callbacks, what say? – Sarthak Mittal May 06 '15 at 17:33
  • @SarthakMittal Apply separation of concerns if your Activity is getting extremely complex. And if you consider your Activity to be a Controller, saying that it `is-a(n)` OnClickListener isn't wrong in my opinion. – Vikram May 10 '15 at 01:21
  • @Vikram so what does "separation of concerns" actually suggests? do you mean that the inner class approach is better(considering that my activity is not just a controller)? – Sarthak Mittal May 10 '15 at 06:24

14 Answers14

53

First, there is no best practice defined by Android regarding registering click listeners. It totally depends on your use case.

Implementing the View.OnClickListener interface to Activity is the way to go. As Android strongly recommends interface implementation over and over again whether it is an Activity or Fragment.

Now as you described :

public class ActivityMain extends Activity implements View.OnClickListener
{
    private class ClickListener implements View.OnClickListener
    {   
        @Override
        public void onClick(View view)
        {
            switch (view.getId())
            {
                //handle multiple view click events
            }
        }
    }
}

This is your approach. Now it is your way of implementation and there is nothing wrong with this if you are not concerned with memory overhead. But what's the benefit of creating the inner class and implementing the View.OnClickListener if you can simply implement that in the main class which can also lead to the code clarity and simplicity that you need.

So it just a discussion rather getting the best possible solution of implementing the View.OnClickListener because if you go with the practical point of everyone, you will go for a solution which is simple and memory efficient.

So I would prefer the conventional way. It keeps things simple and efficient. Check the code below:

@Override
public void onClick(View view)
{
    switch (view.getId())
    {
        //handle multiple view click events
    }
}

P.S : Your approach will definitely increase lines of code :P ;)

Daniel
  • 2,355
  • 9
  • 23
  • 30
kamal vaid
  • 862
  • 7
  • 12
  • 1
    What does LOC mean? – Ali Kazi Oct 07 '16 at 01:16
  • @AliKazi : Sorry i'm bit late . LOC means Line of code – kamal vaid Nov 16 '16 at 06:58
  • You cannot use the `view.getId()` in a switch because each case: requires a value or enum. like `case 1:` or `case ENUM_ITEM:` but `case R.id.myBtn:` is a variable. So you will need to use `if elseif` block statement. If you have many widgets, you end up having an ugly `if elseif` block of code. – Thupten Dec 31 '16 at 20:03
  • 1
    To answer the OP, it may be better to use anonymous onclicklistener or a member instance that implements onclicklistener. In my opinion, it makes code more readable. Sifting through cases of swich and if else block is a little harder to read. – Thupten Dec 31 '16 at 20:11
12

First of all lets get the basics clear here..

By implementing an Interface, your class doesn't become that.. like you said:

"Our Activity-Subclass "Is-a" ClickListener, which ain't completely true."

Your class can only have "Is-a" relationship if it extends, in this case an Activity. Implementing an interface means that it can behave like what interface has set its contract.

An Example:

class Peter extends Human .. means Peter is a Human..

class Peter can also implement programmer, musician, husband etc means Peter can behave as the above.

As for best practice, you could make an entirely separate class which implements OnClickListener like this:

class MyListener implements View.OnClickListener{

  @Override
public void onClick(View view) {
        // do whatever you want here based on the view being passed
    }

}

And in your main Activity you could instantiate MyListener and call onClick() and pass your view in it:

MyListener listener = new MyListener();

Button b = null;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    button = (Button)findViewById(R.id.button);
    listener.onClick(button);
   }
Sharp Edge
  • 4,144
  • 2
  • 27
  • 41
  • 1
    @Sarthak Mittal Have you tried Sharp Edge suggested approach, found any issues? I am also planning to move my Application main Activity listeners to separate classes, as my home activity will react to 10 actions (click, select and other). Also class length is nearly 4K lines, tough for new guy to find out where to change. – Ganesh K May 19 '16 at 07:01
  • Yes that's the whole point, separate the listener code in another class to easily find and make changes. – Sharp Edge May 20 '16 at 13:18
  • inside the onclick() when i added the intent, it skipped my class in which i hv created the object of the MyListeners. How to fix this? – Prajwal Waingankar May 28 '20 at 10:06
  • @PrajwalW what do you mean it skipped your class ? Share the code snippet which you've put inside `onClick()` – Sharp Edge May 29 '20 at 11:45
11

I use button.setOnClickListener(this); where my Activity implements View.OnClickListener, and then get the ID of the Button in a separate method. See below for an example:

public class MyActivity extends ActionBarActivity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.YOUR_LAYOUT);

        ...

        Button myFirstButton = (Button) findViewById(R.id.YOUR_FIRST_BUTTON);
        myFirstButton.setOnClickListener(this);

        Button mySecondButton = (Button) findViewById(R.id.YOUR_SECOND_BUTTON);
        mySecondButton.setOnClickListener(this);

        ...

    }

    ...

    @Override
    public void onClick(View v) {
        Button b = (Button) v;
        switch(b.getId()) {
            case R.id.YOUR_FIRST_BUTTON:
                // Do something
                break;
            case R.id.YOUR_SECOND_BUTTON:
                // Do something
                break;
            ...
        }
    }

    ...

}
Farbod Salamat-Zadeh
  • 19,687
  • 20
  • 75
  • 125
5

Here you can create a btnClickListner object and after that you will call that btnCLickLisner object when ever you want to perform the onCLieck actions for buttons..

Let us assume, in my activity i have a 5 to 10 buttons and writing each button separate onclick listner is bad idea. So to over come this,we can use like below..

register your buttons

Button button1 = (Button)findViewById(R.id.button1);
Button button2 = (Button)findViewById(R.id.button2);
Button button3 = (Button)findViewById(R.id.button3);
Button button4 = (Button)findViewById(R.id.button4);
Button button5 = (Button)findViewById(R.id.button5);

Here i am setting the onclick listner to my buttons after click

button1.setOnClickListener(btnClickListner);
button2.setOnClickListener(btnClickListner);
button3.setOnClickListener(btnClickListner);
button4.setOnClickListener(btnClickListner);
button5.setOnClickListener(btnClickListner);

Here is the btnClick Listner implementation

View.OnClickListener btnClickListner = new OnClickListener()
    {
  @Override
        public void onClick( View v )
        {
            // TODO Auto-generated method stub
            if( button1.getId() == v.getId() )
            {
                //Do Button1 click operations here

            }
            else if( button2.getId() == v.getId() )
            {

               // Do Button2 click operations here

            }
            else if( button3.getId() == v.getId() )
            {
                 // Do Button3 click operations here

            }
            else if( button4.getId() == v.getId() )
            {
                // Do Button4 click operations here

            }
            else if( button5.getId() == v.getId() )
            {
                // Do Button5 click operations here
            }

        }

     }
King of Masses
  • 18,405
  • 4
  • 60
  • 77
4

I have found using Butterknife makes for clean code. And because it uses code generation (not reflections) it has little performance overhead.

public class ActivityMain extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      ButterKnife.inject(this);
  }

  @OnClick(R.id.button_foo)
  void onFoodClicked() {
    // Do some foo
  }

  @OnClick(R.id.button_bar)
  void onBarClicked() {
    // do some bar
  }
}
cyroxis
  • 3,661
  • 22
  • 37
  • Clearly he's not asking for any sort of lib. – Nitesh May 14 '15 at 07:54
  • This is same like adding onClick from xml. – AmmY May 14 '15 at 08:01
  • @AmmY - I would politely disagree with the xml statment. 1. it is not being defined in the xml so the view is not defining the action, and 2. This is lot limited methods on the Activity. – cyroxis May 14 '15 at 21:06
2

For this particular case I'd say that maintain a single instance of a OnClickListener is the best approach for you. You will have a "Has-a" relationship and won't need to create several instances since you are handling the behavior using the view id in the onClick(View view) callback.

public class ActivityMain extends Activity implements View.OnClickListener {

    private View.OnClickListener mClickListener = new View.OnClickListener() {   
        @Override
        public void onClick(View view) {
            switch (view.getId()) {
                //handle multiple view click events
            }
        }
    };

}
Jorge Mendez
  • 674
  • 4
  • 15
2

Your ClickListener is an inner non-static class the coupling of this 'has-a' is no different than if your class Activity implemented View.OnClickListener. This is because your inner ClickListener requires an instance of ActivityMain and really can't be reused. I would argue that you're over engineering and aren't actually gaining anything.

EDIT: To answer your question I like to have anonymous View.OnClickListener for each widget. I think this creates the best separation of logic. I also have methods like setupHelloWorldTextView(TextView helloWorldTextView); where I put all my logic related to that widget.

Charles Durham
  • 2,445
  • 16
  • 17
  • having a separate listener for each widget would really be a sad implementation, and ofcourse it's only my opinion :) – Sarthak Mittal May 06 '15 at 17:27
  • 1
    @SarthakMittal is perfectly justified, decoupling logic into a separate listener object becomes mandatory(with regards to writing clean code) especially if you wish to develop reusable components across activities which use the same click listener logic.Say a simple sort dialog that is used in multiple activities. – humblerookie May 06 '15 at 17:37
2

First approach is better than the other because thats why View.OnClickListener is an Interface instead of an abstract class. besides the later might leak in various situations since you are using a non-static inner class.

Asif Mujteba
  • 4,596
  • 2
  • 22
  • 38
2

A small remark to this, and maybe a little bit of topic.

What, if we not just implement OnClickListener and we have a bunch of other Listeners / Callback to implement. In my Opinion it will get messy to implement all of these in the class instead of using anonymous classes / lambda. It is hard to remember wich method belongs to which interface.

So if we have to implement an interface (in this case OnClickListener) multiple times it my be a good solution to implement on class base and use the switch/case.

But if we have to implement multiple interfaces it may be a good solution to use anonymous classes / lambda

Rene B.
  • 101
  • 6
1

simply you using like not implements subclass or not handle a click event just do like this way .

android.view.View.OnClickListener method_name = new android.view.View.OnClickListener() {

        @Override
        public void onClick(View v) {
            // TODO Auto-generated method stub
            // put your code .
        }
    };

and handle click event into button ya any type of click event like

button_name.setOnClickListener(method_name);

its work very simply Thanks

Hardik Parmar
  • 712
  • 2
  • 13
  • 28
  • yes i know, it works this way, but the question is, what is the best way and WHY? :) – Sarthak Mittal May 11 '15 at 10:44
  • @SarthakMittal ok Fine.. the Best Way is a to implements onclicklistener and using to switch case and each button to do method and method call at switch statement. its very ease and nice i follow this way in my all application. Thanks – Hardik Parmar May 11 '15 at 10:48
  • we all do that, that is the most common implementation, plz read the question properly and then answer. – Sarthak Mittal May 11 '15 at 10:49
1
 public class ProfileDetail extends AppCompatActivity implements View.OnClickListener {

          TextView tv_address, tv_plan;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_profile_detail);



            tv_address = findViewById(R.id.tv_address);
            tv_plan = findViewById(R.id.tv_plan);

            tv_address.setOnClickListener(this);
            tv_plan.setOnClickListener(this);
    }
 @Override
    public void onClick(View view) {
            switch (view.getId()) {
                case R.id.tv_plan:
                    startActivity(new Intent(getApplicationContext(),PlanActivity.class));
                    break;
                case R.id.tv_address:
                     startActivity(new Intent(getApplicationContext(),AddressActivity.class));
                    break;
            }
        }
}
1
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Chronometer chronometer;
    private Button startButton;
    private Button stopButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        chronometer = findViewById(R.id.chronometer);
        startButton =findViewById(R.id.startBtn);
        stopButton = findViewById(R.id.stopBtn);

        startButton.setOnClickListener(this);
        stopButton.setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.startBtn:
                chronometer.start();
                break;
            case R.id.stopBtn:`
                chronometer.stop();
                break;

        }
    }

}
Gerardo Zinno
  • 1,518
  • 1
  • 13
  • 35
0

It really depends on what you want to achieve. If you have e.g. a complex functionality with threading, dependencies, etc., I personally like to decouple it completely from the Activity into a separate class XyzAction, that does the heavy stuff, knows about certain Invokers and returns them results, if needed. My Invokers are basically objects, that implement OnClick/OnTouch/etc.Listeners and bind themselves to needed actions. E.g. there could be a LoginInvoker implementing OnClickListener for a Button and an ImageView and also a generic ActionListener that gets invoked when a MenuItem is clicked. The Invoker has update methods for showing progress to the user and the result of the bound action. The action posts updates to its Invokers and can be garbage collected, if all of them die, because it has no connection to the UI.

For less complex actions, I couple them directly to the Android component (i.e. Activity/Feagment/View) and also call them Actions, with the big difference of them implementing the UI callbacks directly.

In both cases I declare the actions as members, so I can see on a quick glance what specific actions the Android component supports.

If there's something trivial like "show a Toast if button is pressed", I use anonymous inner classes for the UI callbacks, because you normally don't care that much about them with regards to maintainability.

einschnaehkeee
  • 1,858
  • 2
  • 17
  • 20
0
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
Button north,south,east,west;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    init();
    north.setOnClickListener(this);
    south.setOnClickListener(this);
    east.setOnClickListener(this);
    west.setOnClickListener(this);
}

private void init(){
    north = findViewById(R.id.north);
    south = findViewById(R.id.south);
    east = findViewById(R.id.east);
    west = findViewById(R.id.west);
}

@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.north:
            Toast.makeText(MainActivity.this,"NORTH",Toast.LENGTH_SHORT).show();
            break;
        case R.id.south:
            Toast.makeText(MainActivity.this,"SOUTH",Toast.LENGTH_SHORT).show();
            break;
        case R.id.east:
            Toast.makeText(MainActivity.this,"EAST",Toast.LENGTH_SHORT).show();
            break;
        case R.id.west:
            Toast.makeText(MainActivity.this,"WEST",Toast.LENGTH_SHORT).show();
            break;
    }
  }
}
selvabharathi s
  • 137
  • 2
  • 8