Setting a click listener on one button is easy. Setting it on five buttons without turning your Activity into a wall of anonymous classes — that's where people get stuck. If you've ever written setOnClickListener five times and ended up with five nested blocks of code, this post is for you.
We'll cover four approaches — from the simplest lambda for a single button, to the cleanest pattern for handling many buttons at once — all in Kotlin with ViewBinding. No Java, no deprecated switch statements, no implementing interfaces you don't need.
Approach 1 — Lambda (Best for 1–2 Buttons)
For one or two buttons, a simple lambda is the cleanest approach. No interface, no extra method, just inline code:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.btnSubmit.setOnClickListener {
// handle submit click
showToast("Submit clicked")
}
binding.btnCancel.setOnClickListener {
// handle cancel click
showToast("Cancel clicked")
}
}
private fun showToast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
}
findViewById(R.id.btnSubmit) with binding.btnSubmit. It's null-safe, type-safe, and requires zero casting. Enable it in build.gradle with viewBinding { enabled = true }.
Approach 2 — Single Listener with when (Best for 3–6 Buttons)
When you have several buttons doing different things, create one click listener and use Kotlin's when expression to route each button to its action. This is the modern replacement for the old Java switch(v.getId()) pattern:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// Assign the same listener to all buttons
val clickListener = View.OnClickListener { view -> handleClick(view) }
binding.btnOne.setOnClickListener(clickListener)
binding.btnTwo.setOnClickListener(clickListener)
binding.btnThree.setOnClickListener(clickListener)
binding.btnFour.setOnClickListener(clickListener)
}
private fun handleClick(view: View) {
when (view.id) {
R.id.btnOne -> onButtonOneClicked()
R.id.btnTwo -> onButtonTwoClicked()
R.id.btnThree -> onButtonThreeClicked()
R.id.btnFour -> onButtonFourClicked()
}
}
private fun onButtonOneClicked() {
Toast.makeText(this, "Button One", Toast.LENGTH_SHORT).show()
}
private fun onButtonTwoClicked() {
Toast.makeText(this, "Button Two", Toast.LENGTH_SHORT).show()
}
private fun onButtonThreeClicked() {
// navigate to next screen
startActivity(Intent(this, SecondActivity::class.java))
}
private fun onButtonFourClicked() {
// do something else
}
}
Keeping each button's logic in its own private function keeps handleClick() clean and each action independently testable.
Approach 3 — apply Block (Cleanest Setup Code)
If you find the repeated setOnClickListener calls noisy, Kotlin's apply block makes the setup more readable — especially when you're also setting other properties on the buttons:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setupClickListeners()
}
private fun setupClickListeners() {
with(binding) {
btnOne.setOnClickListener { onButtonOneClicked() }
btnTwo.setOnClickListener { onButtonTwoClicked() }
btnThree.setOnClickListener { onButtonThreeClicked() }
btnFour.setOnClickListener { onButtonFourClicked() }
btnFive.setOnClickListener { onButtonFiveClicked() }
}
}
Using with(binding) avoids repeating binding. on every line. All setup is in one method, and onCreate() stays clean.
Approach 4 — Extension Function (Reusable Across the App)
If you frequently set the same click behaviour across multiple screens, an extension function keeps it DRY:
// Create once in a utility file — e.g. ViewExtensions.kt
fun View.onClick(action: () -> Unit) {
setOnClickListener { action() }
}
// Usage — reads like plain English
binding.btnSubmit.onClick { submitForm() }
binding.btnCancel.onClick { cancelAndGoBack() }
binding.btnHelp.onClick { showHelpDialog() }
binding.btnSettings.onClick { openSettings() }
Handling Multiple Clicks in a Fragment
In Fragments, always set click listeners in onViewCreated() — not onCreateView(). And always use viewLifecycleOwner for anything lifecycle-related:
class HomeFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Set all click listeners here — NOT in onCreateView
with(binding) {
btnProfile.setOnClickListener { navigateToProfile() }
btnSettings.setOnClickListener { navigateToSettings() }
btnLogout.setOnClickListener { showLogoutConfirmation() }
btnHelp.setOnClickListener { openHelpCenter() }
}
}
private fun navigateToProfile() {
findNavController().navigate(R.id.action_home_to_profile)
}
private fun navigateToSettings() {
findNavController().navigate(R.id.action_home_to_settings)
}
private fun showLogoutConfirmation() {
// show dialog
}
private fun openHelpCenter() {
// open URL or screen
}
// Always null the binding in onDestroyView to prevent memory leaks
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Bonus - Prevent Double Clicks
A common real-world problem — user taps a button twice quickly and triggers the action twice. A submit button that fires two API calls, a navigation that opens two screens. Here's a clean extension to prevent it:
// Add to ViewExtensions.kt
fun View.setOnSingleClickListener(debounceTime: Long = 600L, action: () -> Unit) {
var lastClickTime = 0L
setOnClickListener {
val now = System.currentTimeMillis()
if (now - lastClickTime >= debounceTime) {
lastClickTime = now
action()
}
}
}
// Usage — drop-in replacement for setOnClickListener
binding.btnSubmit.setOnSingleClickListener {
submitForm() // Only fires once even if tapped multiple times quickly
}
Which Approach Should You Use?
| Approach | Best For | Avoid When |
|---|---|---|
| Lambda | 1–2 buttons, simple actions | 5+ buttons — gets repetitive |
| when expression | 3–6 buttons, all in same class | Actions are complex — extract to separate methods |
| with(binding) | Any number, cleanest setup | Never — this is always a good pattern |
| Extension function | Shared patterns across the app | One-off clicks — overkill for single use |
Best Practices
- Use ViewBinding instead of findViewById — it's null-safe, no casting needed, and catches ID mismatches at compile time instead of runtime. Enable it once in
build.gradleand use it everywhere. - Keep onClick logic out of the listener — the listener should call a named private method, not contain the logic itself.
binding.btnSubmit.setOnClickListener { submitForm() }is far more readable than 10 lines of code inside the lambda. - Set listeners in onViewCreated(), not onCreateView() — in Fragments, the view is guaranteed to be non-null in
onViewCreated(). Setting listeners inonCreateView()can cause subtle issues. - Always null the binding in Fragment's onDestroyView() — binding holds a reference to the view. If you don't null it, the Fragment leaks memory after navigation.
- Use setOnSingleClickListener for form submissions — any button that triggers a network call, navigation, or database write should be protected against double taps. Users tap faster than you think.
Frequently Asked Questions
How do I set OnClickListener for multiple buttons in Kotlin?
Create one click listener, assign it to all buttons, then use a when expression to route each button by its ID. Or use with(binding) to set individual lambdas cleanly. Both approaches are readable and avoid repetition.
What is the difference between setOnClickListener and OnClickListener?
setOnClickListener is the method you call on a View to attach a handler. OnClickListener is the Java interface with the onClick() method. In Kotlin, pass a lambda to setOnClickListener — you rarely need to implement the interface explicitly.
How do I handle button clicks in a Fragment?
Set listeners in onViewCreated() — not onCreateView(). Use ViewBinding for null-safe view access. Always null the binding in onDestroyView() to prevent memory leaks.
How do I prevent double clicks on a button?
Create a setOnSingleClickListener extension function that tracks the last click timestamp and ignores clicks within 600ms. Use it on any button that triggers a network call, navigation, or database write.
- Lambda — best for 1–2 buttons:
binding.btn.setOnClickListener { doSomething() } - when expression — best for 3–6 buttons sharing one listener
- with(binding) — cleanest setup for any number of buttons
- Extension function — reusable patterns across the whole app
- Always use ViewBinding — no
findViewById, no casting, no null crashes - Set listeners in
onViewCreated()in Fragments — notonCreateView() - Null binding in
onDestroyView()to prevent Fragment memory leaks - Use setOnSingleClickListener on submit buttons to prevent double taps
Would u please do one with just two buttons to set on different activites
ReplyDeleteYou can use same type of implementation for both activities. Implement `OnClickListener` interface in activity/fragment. Override `onClick()` method, and assign that listener with `setOnClickListener()` method of buttons.
Deletewhat can i do if i want to do same thing with 3 button . this code dose not work for same thing
ReplyDeleteuse same method or call for those 3 buttons by passing same case into switch.
Deletecase R.id.buttonOne:
case R.id.buttonTwo:
case R.id.buttonThree:
// code for button when user clicks buttonOne.
break;
Perfect!
ReplyDeleteI Get java. Lang. Runtime exception
ReplyDeletecan you be provide more info so I can help?
DeleteI have an error that says "Constant expression required." What can I do about this?
ReplyDelete