18

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?

Graham Borland
  • 60,055
  • 21
  • 138
  • 179
Ashish
  • 191
  • 1
  • 1
  • 6
  • 2
    Please post your code so we can see exactly what you are doing. – Elijah Jul 18 '11 at 22:20
  • Without more information, this question cannot be answered in a reasonable way, and it has sat around with no additional information for quite some time. – Micah Hainline Sep 22 '11 at 21:46

4 Answers4

30

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.

balpha
  • 50,022
  • 18
  • 110
  • 131
  • awesome answer... my problem was somewhat similar to this...upvoting – Shirish Herwade May 29 '14 at 15:39
  • 1
    Great answer, but what do you recommend to get around this. Just post a message to do it? – Ross Hambrick Aug 29 '14 at 18:07
  • @RossHambrick Yeah, you have to be outside the event handler, so that's really the only solution I can think of. – balpha Aug 29 '14 at 18:21
  • @RossHambrick Unless of course it's not actually necessary to make a focus change. In the case that made me notice this issue, I could prevent problems by not changing focus when it wasn't strictly necessary. Depends on the use case. – balpha Aug 29 '14 at 18:31
  • Right. For me, the user is initiating a focus change with a press. As far as I know, there's no way to stop that from happening. Is that your understanding as well? – Ross Hambrick Aug 29 '14 at 20:36
10

I found the solution.

Inside onFocusChange don't call directly requestFocus, instead post a runnable to request focus. e.g. below my code,

editText.setOnFocusChangeListener(new OnFocusChangeListener() {
        public void onFocusChange(View v, boolean hasFocus) {
            if (hasFocus == false) {
                  editText.post(new Runnable() {
                        @Override
                        public void run() {
                            editText.requestFocus();
                        }
                  });
             }
        }
    });
Shirish Herwade
  • 11,461
  • 20
  • 72
  • 111
  • Made no difference in my case. – Tomislav3008 Aug 22 '16 at 09:54
  • @Tomislav3008 why don't you create a question with code showing what you are trying, then may be I can help you – Shirish Herwade Aug 23 '16 at 07:21
  • I managed to put my request focus outside the focusChange handler which imo is the way to go. The problem i had was exactly what balpha explained, getting two editText views with both having focus. Maybe my problem was putting it in a handler instead of a listener, where i had '((EditText)sender).Post(new Runnable(delegate { ((EditText)sender).RequestFocus(); }));' – Tomislav3008 Aug 24 '16 at 08:56
  • @Tomislav3008: well, this answer does exactly this. It moves call to "requestFocus" from the callback to the end of the message queue. The answer from balpha is good explanation of the problem, but it does not suggest a universal solution. The problem is of course, that there maybe many triggers for the focus change. Trying to handle all of them independently is not nice. After all IMO it is design flaw. They should have add "loosingFocus" event, where one could [try to] block it, if needed... – Dmitrii Semikin Jan 19 '21 at 15:19
  • The disadvantage of this solution (IMO) though, is that we don't really prevent loosing focus, but instead we gain it back. Loosing focus could have triggered some additional changes/effects. In such cases we may need to undo these effects or even cannot use this approach at all. – Dmitrii Semikin Jan 19 '21 at 15:21
1

I faced the same problem, one of my EditText has an OnFocusListener and when it lose focus I do some transformations, but if something goes wrong I try to requestFocus again and let the user to fix the problem. Here's when the problem shows up, I end up with to EditText with focus, I tried to search the view with the focus but the findFocus() returned null. The only solution I found was to create a EditText variable

private EditText requestFocus;

If any problem, I set my EditText to that variable and here is what I don't like but it works, I set OnFocusListener to the other views on my activity. When they gain focus, I do this

@Override
public void onFocusChange(View v, boolean hasFocus) {

    if (hasFocus)
        if(requestFocus != null){
            v.clearFocus();
            requestFocus.requestFocus();
            requestFocus = null;
        }
}

I clear the focus from the view that has it, I request Focus and set the variable requestFocus null.

I believe that this is happening because at the time I request a Focus no one has a focus yet, my EditText regain the focus and the the activity gives focus to the next view. Hope it helps to someone.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
0

Try using requestFocus(); http://developer.android.com/reference/android/view/View.html#requestFocus()

locoboy
  • 38,002
  • 70
  • 184
  • 260