Android Spinner
onItemSelected
Initialization
Android Development
UI Components

Android Spinner Avoid onItemSelected calls during initialization

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Spinner fires onItemSelected when it resolves its initial selection, even if the user has not touched the control yet. That behavior surprises many Android developers because the callback sounds like a user event, but it is really a selection-state callback. The practical solution is to separate initialization-time selection from genuine user interaction.

Why the Callback Happens Immediately

A Spinner is always backed by a selected position once it has an adapter. When Android finishes binding the view, it chooses an item and reports that state through onItemSelected.

kotlin
1val items = listOf("One", "Two", "Three")
2val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, items).also {
3    it.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
4}
5
6spinner.adapter = adapter
7spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
8    override fun onItemSelected(
9        parent: AdapterView<*>?,
10        view: View?,
11        position: Int,
12        id: Long
13    ) {
14        Log.d("SpinnerDemo", "Selected position: $position")
15    }
16
17    override fun onNothingSelected(parent: AdapterView<*>?) = Unit
18}

With code like this, the first log entry often appears during screen setup. That is normal framework behavior, not a bug in your listener.

Ignore the First Initialization Event

The simplest pattern is a boolean guard. Skip the first callback, then treat later callbacks as meaningful selections.

kotlin
1private var hasInitializedSpinner = false
2
3spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
4    override fun onItemSelected(
5        parent: AdapterView<*>?,
6        view: View?,
7        position: Int,
8        id: Long
9    ) {
10        if (!hasInitializedSpinner) {
11            hasInitializedSpinner = true
12            return
13        }
14
15        handleUserSelection(position)
16    }
17
18    override fun onNothingSelected(parent: AdapterView<*>?) = Unit
19}
20
21private fun handleUserSelection(position: Int) {
22    Log.d("SpinnerDemo", "User selected $position")
23}

This approach is explicit and easy to read. It works well when the only unwanted callback is the one that happens during initialization.

Attach the Listener After the Initial Selection

Another option is to finish setup first, then attach the listener. This can reduce noise when you know the initial value in advance.

kotlin
1spinner.adapter = adapter
2spinner.setSelection(0, false)
3
4spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
5    override fun onItemSelected(
6        parent: AdapterView<*>?,
7        view: View?,
8        position: Int,
9        id: Long
10    ) {
11        handleUserSelection(position)
12    }
13
14    override fun onNothingSelected(parent: AdapterView<*>?) = Unit
15}

The second argument to setSelection asks Android not to animate the change. Depending on device and lifecycle timing, this method can still be less predictable than the explicit guard flag, so most teams prefer the flag because it documents intent better.

Distinguish User Changes From Programmatic Changes

Initialization is not the only source of extra callbacks. Your own code may call setSelection later, for example when restoring saved state or applying data from a server. In that case, track programmatic changes separately.

kotlin
1private var ignoreNextSelection = false
2
3fun updateSelection(position: Int) {
4    ignoreNextSelection = true
5    spinner.setSelection(position)
6}
7
8spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
9    override fun onItemSelected(
10        parent: AdapterView<*>?,
11        view: View?,
12        position: Int,
13        id: Long
14    ) {
15        if (ignoreNextSelection) {
16            ignoreNextSelection = false
17            return
18        }
19
20        handleUserSelection(position)
21    }
22
23    override fun onNothingSelected(parent: AdapterView<*>?) = Unit
24}

This pattern is clearer than trying to infer user intent from timing. If the app moved the selection, the listener knows why the event happened.

Consider a Placeholder Item

Sometimes the actual UX requirement is not "suppress the first callback," but "the user has not picked a real value yet." In that case, the cleanest design is often a placeholder entry such as Choose one.

kotlin
1val colors = listOf("Choose one", "Red", "Green", "Blue")
2
3spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
4    override fun onItemSelected(
5        parent: AdapterView<*>?,
6        view: View?,
7        position: Int,
8        id: Long
9    ) {
10        if (position == 0) return
11        handleUserSelection(position)
12    }
13
14    override fun onNothingSelected(parent: AdapterView<*>?) = Unit
15}

Now the first entry is a deliberate neutral state rather than an accidental default. That usually makes the UI easier to understand and test.

Common Pitfalls

The first mistake is assuming onItemSelected means a human explicitly tapped the spinner. It does not. The callback only tells you that the selected item changed or was established.

Another common problem is using a single initialization flag and forgetting about later programmatic calls to setSelection. That causes business logic to run during state restore, which can trigger duplicate network requests or confusing UI updates.

A third issue is trying to remove the callback entirely. In most cases you do not want to stop the framework from reporting selection state. You want to filter which selections should drive your app logic.

Summary

  • 'Spinner commonly calls onItemSelected during initialization because initial selection is still a selection.'
  • A boolean guard is the most practical way to ignore the first callback.
  • If your code changes the selection later, track those programmatic updates separately.
  • A placeholder item is often the best UX when no real choice should be selected yet.
  • Treat the listener as a state callback, not a guaranteed user-action callback.

Course illustration
Course illustration

All Rights Reserved.