Android - OnClick Listener in a separate class

Let me share how I code it using MVP. It's the best way to make clean code. Remember each class must have an interface to control it. I will show you the simplest one.

Suppose you want to Toast a text onClick and control it from another class. Here's how it works. Creating interfaces is for nothing but to connect with each other and you can review the code easily.

  1. Create an interface for that MainActivity class.

    public interface MainActivityView {
        void showToast();
    }
    
  2. Create another interface for the Presenter class.

    public interface IMainPresenter<V extends MainActivityView> {
        /*Generic Type is to make sure it comes from MainActivity class only and to avoid other class to access it.*/
        void onAttach(V mainView);
        void onButtonClick();
    }
    

Remember interfaces are nothing but to override method for each class.

  1. Create a Presenter class

    public class MainPresenter<V extends MainActivityView> implements IMainPresenter<V> {
    
        private V mainActivityView;
    
        @Override
        public void onAttach(V mainActivityView) {
            this.mainActivityView=mainActivityView;
        }
    
        public V getView() {
            return mainActivityView;
        }
    
        @Override
        public void onButtonClick() {
            getView().showToast(); //This is the method from MainActivity controlling with this class
        }
    }
    
  2. I'll skip, activity_main.xml layout because there's just a button with id="@+id/buttonId." In MainActivityClass,

    public class MainActivity extends AppCompactActivity implements MainActivityView {
    
    Button btn;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            MainPresenter mainPresenter = new MainPresenter();
            mainPresenter.onAttach(this);
    
            btn = findViewById(R.id.buttonId);
    
            btn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    mainPresenter.onButtonClick(); //Here, check No.3 again!
                }
             });
        }
    
        @Override
        public void showToast() {
             Toast.makeText(this, "Hello", Toast.LENGTH_SHORT).show();
        }
    }
    
  3. All I want to tell you is that. If you create objects in a class, it cannot make unit testing. That's why you're not seeing any new objects calling in android. So, you can use a singleton pattern (Here is Lazy Type) in Presenter class. I'll remove its interface and Generic to see it clearly.

    public class MainPresenter {
    
           private static final MainPresenter mainPresenter = new MainPresenter();
    
           MainPresenter() {}
    
           public static MainPresenter getInstance() {
                   return mainPresenter;
           }
    
           //Some methods here can be get it once you create an object with getInstance();
    }
    

And so you can get its methods from MainActivity like this. Instead of creating objects like this...

    MainPresenter mainPresenter = new MainPresenter();

You can get it like this...

    MainPresenter mainPresenter = mainPresenter.getInstance();

More example for singleton pattern can be found here, https://www.journaldev.com/1377/java-singleton-design-pattern-best-practices-examples

  1. Finally, using static is not a very good choice because it uses memory space whether you use it or not. And so, you can create objects within Application Layer get it with a Typecasting. I'm sure you don't need to unit test that Application layer.

    public class AppLayer extends Application {
    
        private MainPresenter mainPresenter;
    
        @Override
        public void onCreate() {
            super.onCreate();
    
            mainPresenter = new MainPresenter();
        }
    
        public MainPresenter getMainPresenter() {
            return mainPresenter;  
        }
    

And you need to give a class name within Application in manifest.xml

    <application
    android:name=".AppLayer"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">

    </application>

And you can get it with a Typecast in MainActivity like this!

    MainPresenter mainPresenter = ((AppLayer)getApplication()).getMainPresenter();
  1. For further studies, I suggest you learn ButterKnife, Dagger 2 and SOLID Principles. It will help you to create clean coding. Have fun!

Sure, that's possible. Just create a class that implements View.OnClickListener and set that as listener to the View. For example:

public class ExternalOnClickListener implements View.OnClickListener {

    public ExternalOnClickListener(...) {
        // keep references for your onClick logic 
    }

    @Override public void onClick(View v) {
        // TODO: add code here
    }

}

And then set an instance of above class as listener:

view.setOnClickListener(new ExternalOnClickListener(...));

The parameterized constructor is optional, but it's very likely you'll need to pass something through to actually make your onClick(...) logic work on.

Implementing a class anonymously is generally easier to work with though. Just a thought.


Instead of putting the onCLicklistener in a separate class, why dont you try to define onClickListener outside onCreate()??

For e.g: like this

onCreate()

yourViewName.setOnClicklistener(listener):

Outside onCreate()

private OnClickListener listener    =   new OnClickListener() {
        @Override
        public void onClick(View v) {
            // TODO Auto-generated method stub

        }
    };

Yes you can. However, making the listener an inner class has one advantage - it can access the fields and variables of your activity class directly. If you make it a separate class, and your listener actually need to access 5 views, your listener constructor might look like this:

MyListener listener = new MyListener(context, button, textView1, textView2, ratingBar, imageView);

Which is kinda bulky too. If your listener is simple, go ahead and make it a separate class. Otherwise, its up to you for readability.