ScreenshotNeo

BlogGuides

What Is a Callback in Android and How Does It Work?

A callback is code Android invokes after an event or state change. Learn how listeners, lifecycle methods, result callbacks, threads, and cleanup work.

By the ScreenshotNeo team1 October 20267 min read

A callback in Android is code that another component calls later when an event, result, or state change occurs. You provide a listener implementation or override a framework method; Android or a library invokes that code according to its API contract. The callback’s trigger, timing, thread, return value, and cleanup rules come from the specific API.

For example, a click listener runs after a user taps a view, while Activity.onCreate() runs as an activity is created. Both are callbacks because your code is called by a component that controls the event flow.

How the callback pattern works

  1. Obtain the object that owns an event or operation.
  2. Register a listener, pass a callback object, or override a framework method.
  3. Continue with other work. Your code does not call the callback directly.
  4. When the event or result occurs, the owner invokes the callback method.
  5. Use the supplied arguments and follow the method’s return-value and threading contract.

Android’s input-events documentation describes an event listener as an interface in View containing one callback method. See the Android input events overview.

A complete Kotlin click-callback example

This activity registers a callback on a button and updates a text label when Android reports a click.

package com.example.callbackdemo

import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val button = findViewById<Button>(R.id.saveButton)
        val status = findViewById<TextView>(R.id.statusText)

        button.setOnClickListener { view ->
            status.text = "Saved from ${view.id}"
        }
    }
}

The lambda is the callback. Registering it does not execute it; Android calls it when the button receives a click. The equivalent Java form is:

Button button = findViewById(R.id.saveButton);
TextView status = findViewById(R.id.statusText);

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        status.setText("Saved from " + view.getId());
    }
});

Defining your own callback interface

Use a callback when a component should report a result without knowing how the caller will display it. A single-method interface can be represented by a Kotlin function type.

interface LoginCallback {
    fun onComplete(success: Boolean, message: String)
}

class LoginManager {
    fun login(user: String, password: String, callback: LoginCallback) {
        // Replace this with real asynchronous work.
        val success = user.isNotBlank() && password.length >= 8
        callback.onComplete(success, if (success) "Welcome" else "Invalid credentials")
    }
}

class LoginActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val manager = LoginManager()
        manager.login("sam", "password123", object : LoginCallback {
            override fun onComplete(success: Boolean, message: String) {
                // Update UI only if this callback is documented to run on the UI thread.
            }
        })
    }
}

With a function type, the same API can be shorter:

fun login(user: String, password: String, callback: (Boolean, String) -> Unit) {
    val success = user.isNotBlank() && password.length >= 8
    callback(success, if (success) "Welcome" else "Invalid credentials")
}

login("sam", "password123") { success, message ->
    println(message)
}

Activity lifecycle callbacks

An Activity receives lifecycle callbacks as it moves through states. The six core methods are onCreate(), onStart(), onResume(), onPause(), onStop(), and onDestroy(). Android invokes them; your activity overrides them to perform work appropriate to each transition. The activity lifecycle documentation shows the state diagram and callback rules.

class PlayerActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.player)
        // Inflate views and restore saved state.
    }

    override fun onStart() {
        super.onStart()
        // Become visible; register resources needed while visible.
    }

    override fun onResume() {
        super.onResume()
        // Resume foreground interaction, such as a camera preview.
    }

    override fun onPause() {
        // Pause short-lived foreground work.
        super.onPause()
    }

    override fun onStop() {
        // Release resources needed only while visible.
        super.onStop()
    }

    override fun onDestroy() {
        // Final cleanup for this instance.
        super.onDestroy()
    }
}

Keep lifecycle callbacks quick. Android warns that lengthy work in onPause() delays creation of the activity coming to the foreground. Move substantial I/O or computation to an appropriate background mechanism and keep the callback responsible for starting or stopping that work.

Callbacks, return values, and threads

There is no universal callback signature. A click listener returns no value, while many input handlers return a Boolean indicating whether the event was consumed. Read the method documentation before choosing true or false.

Threading is also API-specific. UI callbacks commonly run through the UI event loop, but library result callbacks may use a worker thread or an executor. Do not update views from a background callback. If an API accepts an Executor, choose one explicitly; Android’s API guidance recommends making the invocation thread clear when it is otherwise unspecified.

repository.load(executor) { result ->
    runOnUiThread {
        statusText.text = result.message
    }
}

A result callback can also carry resource obligations. For example, some Google Play services callbacks require you to inspect and release the returned result object according to that API’s reference documentation.

Listener versus callback terminology

Android naming guidance generally uses Listener for a small single-method interface and Callback for a contract with multiple methods, optional defaults, associated constants, or room to grow. The names are conventions, not different execution mechanisms. What matters is the documented trigger, arguments, thread, return value, and lifecycle.

Unregistering callbacks and avoiding leaks

Long-lived objects can retain an activity through a listener. Register and unregister at matching lifecycle boundaries:

private val locationListener = LocationListener { location ->
    // Consume location.
}

override fun onStart() {
    super.onStart()
    locationManager.requestLocationUpdates(provider, 1000L, 10f, locationListener)
}

override fun onStop() {
    locationManager.removeUpdates(locationListener)
    super.onStop()
}
  • Use the same listener instance when an API requires it for removal.
  • Cancel coroutines, jobs, observers, and subscriptions when their owner is destroyed or stopped.
  • Do not capture an activity in a process-wide singleton unless the reference is intentionally scoped.
  • Guard callbacks that can arrive after cancellation or configuration changes.

Common callback errors and fixes

Symptom Likely cause Fix
Callback never runs Listener was registered on the wrong view, registration happened before inflating the layout, or the event never occurred. Verify the view ID, call setContentView() first, and confirm the view is enabled and receives input.
Callback runs twice Registration occurs repeatedly, often in a lifecycle method or recomposition. Register once per owner and remove the previous listener or observer before registering again.
Crash after rotating the device An old activity callback updates a destroyed view. Scope work to the lifecycle, cancel it, and deliver results to the current activity or ViewModel.
Network callback blocks the UI Heavy work runs on the main thread. Move I/O and computation to a background executor or coroutine, then post only the UI update.
Touch handler behavior is wrong The Boolean return value is misunderstood. Read that listener’s contract; return consumed only when your code handled the event.
Memory leak A long-lived publisher retains a listener that captures an activity. Unregister it at the matching lifecycle boundary or use a lifecycle-aware observer.

Testing callback code

Test the component that owns the callback separately from the callback’s effect. For a custom interface, pass a fake implementation and assert its arguments. For a view listener, use an Android instrumented test to perform a click and verify the resulting view state. Also test cancellation, repeated registration, lifecycle transitions, and callbacks that arrive after the screen is stopped.

Performance and reliability checklist

  • Return quickly from lifecycle and input callbacks.
  • Keep callback bodies small; delegate business logic to a testable class.
  • Make threading explicit and marshal UI changes to the main thread.
  • Handle errors and cancellation as callback outcomes, not exceptional afterthoughts.
  • Make callbacks idempotent when duplicate delivery is possible.
  • Remove listeners and release resources at the documented lifecycle point.
  • Log the event, owner state, and request identifier when diagnosing asynchronous timing.

Or skip the browser setup

If you need screenshots of Android documentation, demo pages, or callback states for a project, ScreenshotNeo provides a website screenshot API. One GET request returns PNG, JPEG, WebP, or PDF. Cookie banners, newsletter popups, and chat widgets are removed before capture; bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. Responses identify the page verdict and billing status in headers. Its MCP server lets Claude, Cursor, and other AI agents use take_screenshot, get_page_info, and capture_pdf.

See the ScreenshotNeo API docs for all options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://developer.android.com/develop/ui/views/touch-and-input/input-events -o android-callbacks.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://developer.android.com/develop/ui/views/touch-and-input/input-events"}, timeout=90)
open("android-callbacks.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://developer.android.com/develop/ui/views/touch-and-input/input-events' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 shots, and every feature is available on every plan. Create a free ScreenshotNeo account.

FAQ

Is a callback the same as a coroutine?

No. A callback is an invocation pattern. A coroutine is a concurrency tool that can produce results without nesting callbacks; either approach still has lifecycle and threading rules.

Can a callback return a result?

Yes, when its interface defines a return value. The meaning is specific to that API, such as whether an input event was consumed.

Why does callback timing matter?

Callbacks may run after the initiating method returns, on another thread, or after the screen state changes. Code must handle stale owners, cancellation, and the documented executor.

Should every callback be removed?

Remove registrations whose owner or resource has a shorter lifetime than the publisher. A one-shot local callback may not need explicit removal; a listener on a long-lived service usually does.