Android UI, Activities & Navigation

Handling User Input & Events

18 min Lesson 3 of 12

Handling User Input & Events

Every useful Android application reacts to what the user does — taps a button, types into a field, checks a box. Android expresses all of these interactions through a single concept: the event listener. A listener is an object that implements a callback interface; you register it on a view, and the framework calls it when the user triggers that event. This lesson shows you how to attach listeners, read back what the user typed, and combine both to build a realistic interactive screen — all in Java.

The View Reference Problem

Before you can listen to a view you need a Java reference to it. Views are declared in XML and inflated by setContentView(). The bridge between XML and Java is findViewById():

// Inside Activity.onCreate(), AFTER setContentView(R.layout.activity_main) Button submitBtn = findViewById(R.id.btn_submit); EditText nameField = findViewById(R.id.et_name); TextView resultText = findViewById(R.id.tv_result);

R.id.* constants are auto-generated from the android:id attributes in your layout XML. The call returns the concrete view type, so assign it to the matching class (Button, EditText, TextView, etc.).

Always call setContentView() before findViewById(). The layout is not inflated until that call; calling findViewById() before it always returns null, causing a NullPointerException the moment you try to use the reference.

Click Listeners

The most common event in any app is a button tap. The interface is View.OnClickListener; it has exactly one method: onClick(View v). There are three idiomatic ways to attach one in Java:

Option 1 — Anonymous Inner Class

Classic Java style, verbose but explicit:

submitBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // code runs on the main thread when the button is tapped resultText.setText("Button tapped!"); } });

Option 2 — Lambda (Java 8+, recommended)

Because OnClickListener is a functional interface (single abstract method), a lambda is a clean drop-in:

submitBtn.setOnClickListener(v -> { resultText.setText("Button tapped!"); });
Use lambdas for single-method listeners. They cut boilerplate significantly and keep the logic right next to the registration. Reserve anonymous classes or named inner classes for cases where you need to reference the listener object itself (e.g. to call removeCallbacks).

Option 3 — Activity Implements the Interface

When an activity manages many buttons it can implement View.OnClickListener itself and dispatch by view ID:

public class MainActivity extends AppCompatActivity implements View.OnClickListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button btnSave = findViewById(R.id.btn_save); Button btnCancel = findViewById(R.id.btn_cancel); btnSave.setOnClickListener(this); btnCancel.setOnClickListener(this); } @Override public void onClick(View v) { int id = v.getId(); if (id == R.id.btn_save) { // handle save } else if (id == R.id.btn_cancel) { // handle cancel } } }

Reading Text from EditText

An EditText holds the user's typed text. You retrieve it as a CharSequence via getText(), then convert to a plain String with toString(). Always trim whitespace before validating:

String name = nameField.getText().toString().trim(); if (name.isEmpty()) { nameField.setError("Name is required"); // shows a built-in red error popup return; } resultText.setText("Hello, " + name + "!");

setError() is the standard way to communicate a field-level validation failure; it shows a red exclamation icon and a tooltip without any extra layout code.

Other Common Input Views

Beyond buttons and text fields, you will frequently work with:

  • CheckBoxisChecked() returns a boolean. Listen for changes with setOnCheckedChangeListener().
  • RadioGroup / RadioButton — call radioGroup.getCheckedRadioButtonId() to find the selected option ID, then findViewById(id) to get the button text.
  • Switch — same setOnCheckedChangeListener() as CheckBox.
  • SpinnersetOnItemSelectedListener() callback; getSelectedItem().toString() gives the current value.

A Realistic Example: Profile Form

Here is a complete, self-contained example that ties everything together — layout XML followed by the Activity Java code:

res/layout/activity_profile.xml (relevant excerpt):

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp"> <EditText android:id="@+id/et_username" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Username" android:inputType="textPersonName" /> <EditText android:id="@+id/et_email" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Email" android:inputType="textEmailAddress" /> <CheckBox android:id="@+id/cb_newsletter" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Subscribe to newsletter" /> <Button android:id="@+id/btn_save" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Save Profile" /> <TextView android:id="@+id/tv_output" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp" /> </LinearLayout>

ProfileActivity.java:

import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; public class ProfileActivity extends AppCompatActivity { private EditText etUsername; private EditText etEmail; private CheckBox cbNewsletter; private TextView tvOutput; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_profile); // 1. Get references etUsername = findViewById(R.id.et_username); etEmail = findViewById(R.id.et_email); cbNewsletter = findViewById(R.id.cb_newsletter); tvOutput = findViewById(R.id.tv_output); Button btnSave = findViewById(R.id.btn_save); // 2. Attach listener btnSave.setOnClickListener(v -> onSaveClicked()); } private void onSaveClicked() { String username = etUsername.getText().toString().trim(); String email = etEmail.getText().toString().trim(); // 3. Validate if (username.isEmpty()) { etUsername.setError("Username is required"); etUsername.requestFocus(); return; } if (email.isEmpty() || !android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()) { etEmail.setError("Valid email is required"); etEmail.requestFocus(); return; } // 4. React String summary = "Saved: " + username + " <" + email + ">"; if (cbNewsletter.isChecked()) { summary += "\nNewsletter: subscribed"; } tvOutput.setText(summary); } }
Delegate event logic to a named private method (onSaveClicked() above) rather than writing all the logic inside the lambda. The lambda stays one line, and the real logic is easy to unit-test and read.

TextWatcher — Reacting as the User Types

Sometimes you need to react to every keystroke — for example to enable a button only when the form is valid, or to show a character counter. Use TextWatcher:

etUsername.addTextChangedListener(new android.text.TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override public void onTextChanged(CharSequence s, int start, int before, int count) {} @Override public void afterTextChanged(android.text.Editable s) { // called after every change; s.toString() is the current text btnSave.setEnabled(s.toString().trim().length() > 0); } });

You must implement all three methods of the interface; the ones you do not need can be left empty.

The Main Thread Rule

Every listener callback — onClick, afterTextChanged, onCheckedChanged — executes on the main (UI) thread. You can read and write views freely, but you must never do blocking work here (network requests, disk I/O, long computations). Blocking the main thread for more than ~5 seconds triggers an ANR (Application Not Responding) dialog. Move heavy work to a background thread or use AsyncTask / ExecutorService / LiveData.

Never perform network or database calls directly inside a listener callback. If you do, the UI freezes and the system may kill the app. Always dispatch to a background thread, then post UI updates back with runOnUiThread() or via a Handler.

Summary

User interaction in Android flows through listener interfaces registered on views. The key workflow is: inflate your layout with setContentView(), obtain view references with findViewById(), attach a listener (preferably a lambda), read input with getText().toString().trim(), validate, and update the UI. Keep listener callbacks thin — delegate real logic to private methods and never block the main thread. These patterns form the foundation for every interactive screen you will build.