Android EditText Crashes - Common Pitfalls and How to Fix Them

You added one small attribute to an EditText. The app compiled fine. You ran it, typed a few characters, and — crash. No warning, no obvious reason, just a stacktrace pointing somewhere deep in the Android framework.

EditText is one of the most-used components in Android and also one of the most quietly dangerous. It inherits from TextView but doesn't always behave like one. It has edge cases around input types, text watchers, and focus handling that cause crashes in ways that feel completely unexpected the first time you hit them.

This post covers the most common EditText crashes — including the classic textAllCaps trap — with the exact fix for each one.

Android EditText IndexOutOfBounds crash fix

EditText looks simple. It has opinions.

Crash 1 — IndexOutOfBoundsException with textAllCaps

This is the most common one. You have a form where all input should be uppercase — a vehicle number field, an ID input, a promo code box. You apply textAllCaps because it works perfectly on TextViews. The app crashes the moment the user types.

The crash

java.lang.IndexOutOfBoundsException: setSpan (X ... Y) ends beyond length Z
    at android.text.SpannableStringBuilder.checkRange(SpannableStringBuilder.java)

Why it happens

textAllCaps is a TextView-only property. It works by replacing the text with an AllCapsTransformationMethod that transforms displayed text to uppercase. But EditText manages its own cursor and span positions internally — when the transformation shifts character positions, the cursor index goes out of bounds. EditText inherits textAllCaps from TextView but was never designed to work with it.

❌ Wrong

<!-- This WILL crash when the user types -->
<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textAllCaps="true" />

✅ Fix — use inputType instead

<!-- Forces uppercase keyboard input — no crash -->
<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:inputType="textCapCharacters" />

Or in Kotlin if you need to set it programmatically:

// Programmatic equivalent of textCapCharacters
binding.etInput.inputType = InputType.TYPE_CLASS_TEXT or
        InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS
💡 inputType capitalization options:
  • textCapCharacters — ALL CAPS
  • textCapWords — First Letter Of Each Word
  • textCapSentences — First letter of each sentence

Crash 2 — StackOverflowError in TextWatcher

You add a TextWatcher to format input as the user types — a phone number, a credit card, a currency field. You call setText() inside afterTextChanged(). The app freezes then crashes with a StackOverflowError.

Why it happens

Calling setText() inside afterTextChanged() triggers afterTextChanged() again. Which calls setText() again. Which triggers afterTextChanged() again. Infinite loop. Stack overflow.

❌ Wrong

binding.etPhone.addTextChangedListener(object : TextWatcher {
    override fun afterTextChanged(s: Editable?) {
        // Calling setText here re-triggers afterTextChanged — infinite loop!
        binding.etPhone.setText(formatPhoneNumber(s.toString()))
    }
    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
})

✅ Fix - use a guard flag

var isFormatting = false

binding.etPhone.addTextChangedListener(object : TextWatcher {
    override fun afterTextChanged(s: Editable?) {
        if (isFormatting) return  // Guard — prevents re-entry
        isFormatting = true

        val formatted = formatPhoneNumber(s.toString())
        binding.etPhone.setText(formatted)
        // Move cursor to end after formatting
        binding.etPhone.setSelection(formatted.length)

        isFormatting = false
    }
    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
})

Crash 3 — Memory Leak from TextWatcher in Fragment

This one doesn't crash immediately — it leaks memory silently. Eventually your app slows down, consumes excess RAM, and may crash from an OutOfMemoryError after extended use. The culprit is a TextWatcher that holds a reference to the Fragment after the Fragment's view is destroyed.

Why it happens

TextWatchers are registered on the EditText view. If you add a TextWatcher in onViewCreated() but never remove it, the TextWatcher keeps a reference to your Fragment (through this or lambda capture). When the Fragment navigates away, the view is destroyed but the TextWatcher — and therefore the Fragment — can't be garbage collected.

❌ Wrong

class SearchFragment : Fragment() {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // Added but never removed — leaks Fragment reference
        binding.etSearch.addTextChangedListener(object : TextWatcher {
            override fun afterTextChanged(s: Editable?) {
                performSearch(s.toString()) // captures Fragment context
            }
            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
        })
    }
}

✅ Fix — remove in onDestroyView()

class SearchFragment : Fragment() {

    private val searchWatcher = object : TextWatcher {
        override fun afterTextChanged(s: Editable?) {
            performSearch(s.toString())
        }
        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.etSearch.addTextChangedListener(searchWatcher)
    }

    override fun onDestroyView() {
        super.onDestroyView()
        // Always remove TextWatcher when view is destroyed
        binding.etSearch.removeTextChangedListener(searchWatcher)
    }
}

Crash 4 — NullPointerException on getText()

You call editText.text.toString() and get a NullPointerException. Seems impossible — text should always return something. But there's one case where it returns null: when the EditText hasn't been fully initialised yet, or when you're accessing it before the layout is inflated.

✅ Fix — use safe call or orEmpty()

// Safe — never throws NPE
val input = binding.etInput.text?.toString().orEmpty()

// Or with a trim for form validation
val input = binding.etInput.text?.toString()?.trim() ?: ""

// Kotlin extension to make it cleaner everywhere
fun EditText.value(): String = text?.toString()?.trim() ?: ""

// Usage
val email = binding.etEmail.value()
val password = binding.etPassword.value()

Crash 5 — Cursor Crash with Mixed inputType Flags

You set inputType to a combination of flags that conflict with each other. The most common example: combining textPassword with textMultiLine. The keyboard behaves strangely, the cursor jumps, and on some devices it crashes.

Why it happens

textPassword hides characters and disables multi-line input at the OS level. Combining it with textMultiLine creates a contradictory state that different OEM keyboards handle differently — some crash, some just misbehave.

❌ Wrong

<!-- textPassword and textMultiLine are contradictory -->
<EditText
    android:inputType="textPassword|textMultiLine" />

✅ Fix — pick one or use numberPassword for PINs

<!-- Password field — single line only -->
<EditText
    android:inputType="textPassword" />

<!-- Numeric PIN -->
<EditText
    android:inputType="numberPassword" />

<!-- Multi-line notes field -->
<EditText
    android:inputType="textMultiLine"
    android:minLines="3"
    android:maxLines="6"
    android:gravity="top|start" />

Quick Reference — Common inputType Values

Use Case inputType
Email address textEmailAddress
Password textPassword
Numeric PIN numberPassword
Phone number phone
Decimal number numberDecimal
ALL CAPS text textCapCharacters
Multi-line text textMultiLine
URL textUri

Best Practices

  • Never use textAllCaps on EditText — use inputType="textCapCharacters" instead. This is the single most common EditText crash and it's 100% avoidable.
  • Always guard TextWatcher with a flag — any time you call setText() inside a TextWatcher, wrap the logic in an isFormatting guard to prevent infinite recursion.
  • Always remove TextWatcher in onDestroyView() — store your TextWatcher as a property and remove it when the Fragment's view is destroyed to prevent memory leaks.
  • Use safe calls on getText()editText.text?.toString().orEmpty() is safer than editText.text.toString(). Create an extension function EditText.value() and use it everywhere.
  • Don't mix contradictory inputType flagstextPassword and textMultiLine don't mix. Read the Android docs for which flags are compatible before combining them.

Frequently Asked Questions

Why does textAllCaps crash EditText?
textAllCaps uses a TransformationMethod designed for TextView that shifts character positions. EditText manages cursor spans internally — when those positions go out of sync, it throws IndexOutOfBoundsException. Use inputType="textCapCharacters" instead.

How do I make EditText input uppercase?
Use android:inputType="textCapCharacters" in XML, or InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS in Kotlin. This tells the keyboard to show uppercase without modifying spans internally.

How do I fix StackOverflowError in TextWatcher?
It happens when you call setText() inside afterTextChanged(), triggering an infinite loop. Add a boolean isFormatting guard — return early if it's true, set it before calling setText(), and reset it after.

How do I prevent memory leaks with TextWatcher in Fragment?
Store the TextWatcher as a Fragment property and call removeTextChangedListener() in onDestroyView(). This releases the reference to the Fragment so it can be garbage collected when the view is destroyed.

📝 Summary
  • textAllCaps on EditText → use inputType="textCapCharacters" instead
  • StackOverflowError in TextWatcher → add isFormatting guard before calling setText()
  • TextWatcher memory leak in Fragment → store as property, remove in onDestroyView()
  • NullPointerException on getText() → use text?.toString().orEmpty() or an extension function
  • Conflicting inputType flags → never combine textPassword with textMultiLine

Pragnesh Ghoda

A forward-thinking developer offering more than 8 years of experience building, integrating, and supporting android applications for mobile and tablet devices on the Android platform. Talks about #kotlin and #android

Post a Comment

Please let us know about any concerns or query.

Previous Post Next Post

Contact Form