Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Focus issue with multiple EditTexts

I have an activity with two EditTexts. I am calling the requestFocus on the second EditText field since by default the focus goes to the first one. The focus appears to be in the second field (the second one gets the highlighted border), but if we try to enter any characters using the hardware keyboard the text appears in the first EditText control. Any ideas why it would be happening?

like image 498
Ashish Avatar asked Jun 09 '10 04:06

Ashish


1 Answers

It's hard to tell whether this was your problem, but it's not unlikely.

TL;DR: Never call focus-changing methods like requestFocus() from inside a onFocusChanged() call.

The issue lies in ViewGroup.requestChildFocus(), which contains this:

// We had a previous notion of who had focus. Clear it.
if (mFocused != child) {
    if (mFocused != null) {
        mFocused.unFocus();
    }

    mFocused = child;
}

Inside the private field mFocused a ViewGroup stores the child view that currently has focus, if any.

Say you have a ViewGroup VG that contains three focusable views (e.g. EditTexts) A, B, and C.

You have added an OnFocusChangeListener to A that (maybe not directly, but somewhere nested inside) calls B.requestFocus() when A loses focus.

Now imagine that A has focus, and the user taps on C, causing A to lose and C to gain focus. Because VG.mFocused is currently A, the above part of VG.requestChildFocus(C, C) then translates to this:

if (A != C) {
    if (A != null) {
        A.unFocus();          // <-- (1)
    }

    mFocused = C;             // <-- (3)
}

A.unFocus() does two important things here:

  1. It marks A as not having focus anymore.

  2. It calls your focus change listener.

In that listener, you now call B.requestFocus(). This causes B to be marked as having focus, and then calls VG.requestChildFocus(B, B). Because we're still deep inside the call I've marked with (1), the value of mFocused is still A, and thus this inner call looks like this:

if (A != B) {
    if (A != null) {
        A.unFocus();
    }

    mFocused = B;             // <-- (2)
}

This time, the call to A.unFocus() doesn't do anything, because A is already marked as unfocused (otherwise we'd have an infinite recursion here). Also, nothing happens that marks C as unfocused, which is the view that actually has focus right now.

Now comes (2), which sets mFocused to B. After some more stuff, we finally return from the call at (1), and thus at (3) the value of mFocused is now set to C, overwriting the previous change.

So now we end up with an incosistent state. B and C both think they have focus, VG considers C to be the focused child.

In particular, keypresses end up in C, and it is impossible for the user to switch focus back to B, because B thinks it already has focus and thus doesn't do anything on focus requests; most importantly, it does not call VG.requestChildFocus.

Corollary: You also shouldn't rely on results from hasFocus() calls while inside an OnFocusChanged handler, because the focus information is inconsistent while inside that call.

like image 81
balpha Avatar answered Sep 22 '22 03:09

balpha