react-native: Crash on email detection in TextInput on Xiaomi devices running android 10

Xiaomi devices running android 10 show a little popup with the Text Frequent email when they detect a valid email address in the text input currently selected. This feature causes some crashes on my react native app. Sadly I can’t reproduce in on an empty project, but it always happens on some inputs in my app. I have not been able to determine the difference between the textinputs that causes the problem and the textinput that does not cause any crash.

React Native version: 0.61.2

Steps To Reproduce

  1. Select a textinput
  2. digit a valid email address (such as test@test.com). As soon as I type the last c (creating a valid email format), the app crashed

The error:

java.lang.NullPointerException · Attempt to invoke direct method ‘void android.widget.Editor$SelectionModifierCursorController.initDrawables()’ on a null object reference

The full stack trace:

java.lang.NullPointerException: Attempt to invoke direct method 'void android.widget.Editor$SelectionModifierCursorController.initDrawables()' on a null object reference
        at android.widget.Editor$SelectionModifierCursorController.access$300(Editor.java:6696)
        at android.widget.Editor.getEmailPopupWindow(Editor.java:1469)
        at android.widget.Editor.showEmailPopupWindow(Editor.java:1477)
        at android.widget.Editor.handleEmailPopup(Editor.java:1456)
        at android.widget.Editor.updateCursorPosition(Editor.java:2099)
        at android.widget.TextView.getUpdatedHighlightPath(TextView.java:7813)
        at android.widget.TextView.onDraw(TextView.java:7998)
        at android.view.View.draw(View.java:21472)
        at android.view.View.updateDisplayListIfDirty(View.java:20349)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4396)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4369)
        at android.view.View.updateDisplayListIfDirty(View.java:20309)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4396)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4369)
        at android.view.View.updateDisplayListIfDirty(View.java:20309)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4396)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4369)
        at android.view.View.updateDisplayListIfDirty(View.java:20309)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4396)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4369)
        at android.view.View.updateDisplayListIfDirty(View.java:20309)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4396)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4369)
        at android.view.View.updateDisplayListIfDirty(View.java:20309)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4396)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4369)
        at android.view.View.updateDisplayListIfDirty(View.java:20309)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4396)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4369)
        at android.view.View.updateDisplayListIfDirty(View.java:20309)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4396)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4369)
        at android.view.View.updateDisplayListIfDirty(View.java:20309)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4396)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4369)
        at android.view.View.updateDisplayListIfDirty(View.java:20309)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4396)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4369)
        at android.view.View.updateDisplayListIfDirty(View.java:20309)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4396)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4369)
        at android.view.View.updateDisplayListIfDirty(View.java:20309)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4396)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4369)
        at android.view.View.updateDisplayListIfDirty(View.java:20309)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4396)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4369)
        at android.view.View.updateDisplayListIfDirty(View.java:20309)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4396)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4369)
        at android.view.View.updateDisplayListIfDirty(View.java:20309)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4396)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4369)
        at android.view.View.updateDisplayListIfDirty(View.java:20309)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4396)
2019-11-12 17:19:57.876 20111-20111/com.yourvoice.ccApp.dev E/AndroidRuntime:     at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4369)
        at android.view.View.updateDisplayListIfDirty(View.java:20309)
        at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:575)
        at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:581)
        at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:654)
        at android.view.ViewRootImpl.draw(ViewRootImpl.java:3687)
        at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:3482)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2819)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1782)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7785)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1031)
        at android.view.Choreographer.doCallbacks(Choreographer.java:854)
        at android.view.Choreographer.doFrame(Choreographer.java:789)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1016)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:221)
        at android.app.ActivityThread.main(ActivityThread.java:7520)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 70
  • Comments: 82 (4 by maintainers)

Commits related to this issue

Most upvoted comments

I had to set caretHidden to true and it’s not crashing the app now (tested on Redmi K30). The downside is you can’t use the context menu actions. I am okay though with the trade off (for now) considering I’ll be using it only on email address inputs.

<TextInput
    caretHidden
    autoCapitalize='none'
    autoCorrect={false}
    keyboardType='email-address'
    autoCompleteType='email'
/>

My hack:

import { Platform, TextInput } from 'react-native';

const { Version } = Platform;

const brandsNeedingWorkaround = ['redmi', 'xiaomi', 'poco', 'pocophone'];
const needsXiaomiWorkaround = brandsNeedingWorkaround.includes(RNInfo.getBrand().toLowerCase())
    && Version > 28;

// https://github.com/facebook/react-native/issues/27204
const InputTextWrapper = forwardRef(({ onFocus, caretHidden, ...others }, ref) => {
    const [hackCaretHidden, setHackCaretHidden] = useState(needsXiaomiWorkaround ? true : caretHidden);

    const handleFocus = useCallback(() => {
        if (needsXiaomiWorkaround) {
            setHackCaretHidden(caretHidden);
        }
        if (onFocus) onFocus();
    }, [onFocus, caretHidden]);

    return (
        <TextInput
            ref={ref}
            {...others}
            onFocus={handleFocus}
            caretHidden={hackCaretHidden}
        />
    );
});

Same issue here. User gave one star review for this. 😢

For temperary fix you can use like this as mentioned at https://github.com/facebook/react-native/issues/20887#issuecomment-416775306 :

..
..
constructor(props) {
    super(props)
    
     this.state = { editable: false }
  }

  componentDidMount() {
      setTimeout(() => { this.setState({ editable: true }) }, 100);
  }

  render() {
    return (
         ...
         ... 
            <TextInput editable={this.state.editable} />
        ...
        ...
      )
   }
}

It’s temperary but it works for now. But of course it should be fixed soon as possible.

We also fixed this hacky with a hooks version of https://github.com/facebook/react-native/issues/20887#issuecomment-416775306

It works but it breaks the autoFocus prop.

const [editable, setEditable] = React.useState(false)

React.useEffect(() => {
  React.useEffect(() => {
  setTimeout(() => {
    setEditable(true)
  }, 100)
}, [])

return (
  <TextInput
    editable={editable}
    ...
  />
)

Same problem here. It only happens on Xiaomi devices running Android 10.

Same problem here, none of the proposed solutions worked in my production app. I have several thousands of users so far (released a few days ago). The models MI 9 (cepheus), Redmi K20 Pro (raphael), and Mi 9T (davinci), are experiencing this issue, they can’t even create an account on my app. And the 1 star review keep coming…

Yes even if caretHidden is a workaround, it cannot be an acceptable solution as you won’t be able to see the cursor in the EditText anymore 😞

Keep in mind that this crash happens because of how MIUI is rendering email suggestion box - so it’s not limited strictly to Xiaomi branded phones, it can happen also on phones that are made by “sister” brands like Redmi and Poco so when you are creating check if caret needs to be turned off (or any other workaround that works for you) do not include only xiaomi in check but also the rest of miui family. I don’t have any pocophone or know someone with one so I can’t tell for sure what BUILD.Brand is reported there so my check looks like this

const brandsNeedingWorkaround = ['redmi', 'xiaomi', 'poco', 'pocophone']
const needsXiaomiWorkaround =
    type === 'email' &&
    brandsNeedingWorkaround.includes(getDeviceBrand().toLowerCase()) &&
    getDeviceOSVersion() > 28

I found the problem. This is a bug in the mi system on android 10, because it will have a prompt view when you type an email in the input box.When we put the input box in the scroll view, the system failed to locate the prompt view properly, causing the application to crash.If the input field is outside of the scroll view you can avoid this problem and hopefully this will help you out, this is not the optimal solution.Because it might require us to change our layout.That’s what I’m going to do.

Should be fixed in next version of react native.

Based on commit 07a597a

Oh, I’m sorry that the previous answer was problematic. Please remove the removeClippedSubviews property from your ScrollView component. This is the cause of the problem

It took me some time to find out that if I type a . (dot) in the email input field and then clear it, after that typing the email does not crash the app anymore. So here is yet another workaround that does not include caret hiding and still working for me on Redmi 9.

const [email, setEmail] = useState(".");

useEffect(() => {
  setTimeout(() => {
    setEmail("");
  }, 1);
}, []);

Same issue here on a MI 9. Setting caretHidden={true} is the only solution that works but really destroys the user experience.

It would be great if this issue could get some attention from the React Native team. I initially fixed it using the timeout/editable workaround described here and in #20887, but since we migrated from Expo 34 to Expo 36 (React Native 0.61.5), it has returned and the workaround doesn’t seem to work anymore.

This is exactly the type of bug that causes terrible user experiences, even if it’s limited to a set of devices. The users impacted are quick to churn and write 1-star reviews.

Removing <item name="android:windowTranslucentStatus">true</item> from styles.xml on Android side fixes the issue.

Works for me, not optimal but a seamless solution for me.

  const [caretLoaded, setCaretLoaded] = useState(true)

  useEffect(() => {
    setCaretLoaded(false)
  }, [])
 
  return (
      <InpupText
          caretHidden={caretLoaded}
      />
  )

Actually it’s still crashing on some Mi devices because the fix https://github.com/facebook/react-native/commit/07a597ad185c8c31ac38bdd4d022b0b880d02859 only works for phones who have exactly “Xiaomi” as Manufacturer, but there are more Xiaomi brands affected (Poco, Redmi etc. and some weird unique names for some new models).

I made a pull request that should cover more devices: https://github.com/facebook/react-native/pull/30109

Feel free to add more if you see that some are missing, I just took the ones I found in my app’s error reports.

We have a similar issue with users who have Xiaomi. Does anyone know how to test this without actual device? Any emulator which acting like this?

just happened to experience it as well in my Redmi Note 8 Pro

The only solution that works is applying caretHidden. Try to set timeout in editable doesn’t work

Hope this can get fixed asap

setting removeClippedSubviews={false} on the container view fixed the problem for me

having the same issue =(

This issue was fixed on React native 0.63.3 go npx react-native upgrade

As mentioned by me and @AndreasA, its NOT fixed on 0.63.3, for many reasons.

It seems this issue was fixed on React native 0.63.3, could someone double-check?

https://github.com/react-native-community/releases/blob/master/CHANGELOG.md#fixed 07a597a

This issue was fixed on React native 0.63.3 go npx react-native upgrade

Just wondering but does that fix actually work because on one of my team’s test devices the issue also happens if keyboardType is set to default as soon as the user enters something that resembles an email address and from what I can see that is basically what this fix does?

Finally 😄 https://github.com/facebook/react-native/commit/07a597ad185c8c31ac38bdd4d022b0b880d02859

Its not only on Xiaomi (as mentioned in https://github.com/facebook/react-native/issues/27204#issuecomment-666344168), and the problem happens even when is not type email-address, because the MIUI detects email automatically.

I had the same problem. I had to remove keyboardType=‘email-address’ to get rid of the crashing

+1, i use the carretHide but i need a better solution

Sharing our solution for this problem that uses the editable solution listed before but also works (in our tests) for autoFocus. Setting caretHidden to true does work but it makes for a jarring UI. Using the editable workaround the user experience is not changed.

This is our text field component that we use instead of calling TextInput directly:

import { Platform, TextInput } from 'react-native'
import { getBrand } from 'react-native-device-info'

const EMAIL_CRASH_WORKAROUND_BRANDS = ['m2002j9g', 'm2002j9g', 'm2004j19c', 'm2007j20cg', 'mi', 'mi', 'mix', 'poco', 'pocophone', 'redmi', 'xiaomi']
const EMAIL_CRASH_WORKAROUND_NEEDED = EMAIL_CRASH_WORKAROUND_BRANDS.includes(getBrand().toLowerCase()) && Platform.Version > 28

I stripped down our code to show the relevant methods of the component:

// allows parent to set focus to the input
focus(){
	// console.log('set focus to text field')
	// set flag so we know if focus() was called
	this.wasFocusHandlerCalled = true 
	this.input.focus()
},

// capture reference to the input
setRef(ref){
	this.input = ref
},

render(){
	let attribs = {}
	// email crash workaround
	if(EMAIL_CRASH_WORKAROUND_NEEDED){
		if(this.state && this.state.emailCrashEditable)
			attribs.editable = 'editable' in this.props ? this.props.editable : true
		else
			attribs.editable = 'editable' in this.props ? !this.props.editable : false

		// console.log(`editable for ${this.props.fieldName}:`, attribs.editable)
	}

	return (
		<TextInput 				
			ref={this.setRef}
			{... this.props}
			{ ... attribs} 
		/>
	)
}

Finally the most important part. We toggle the editable prop of the input once the component is loaded. We check for the autoFocus prop here and set focus if necessary. A parent component using a ref to this component could call focus() on it so we also check if that was called and set focus if necessary:

componentDidMount(){
	// 'editable' email crash workaround 
	if(EMAIL_CRASH_WORKAROUND_NEEDED){
		setTimeout(() => {
			if(this.hasUnmounted) return // set this in componentWillUnmount

			// console.log('set emailCrashEditable')
			this.setState({ emailCrashEditable: true }, () => {
				if(this.props.autoFocus || this.wasFocusHandlerCalled){
					// when focus was set by calling the focus handler then we need to blur it first
					if(this.wasFocusHandlerCalled)
						this.input.blur()

					this.input.focus()
				}
			})
		}, 100)
	}
}

Looking forward to an actual fix for this in the core code so that we can get rid of this kludgy workaround.

See comments above.

That fix does not necessarily fix it as using default keyboard type does not ensure the issue does not happen. It occurs as soon as an email address like string is inputed. Also it does not take into account all Xiaomi UI devices. And it is actually more of a workaround than a solution.

Until this issue is fixed & released, here is a simple solution for it:

const deviceName = Device.manufacturer;

    if (deviceName === "Xiaomi") {
      this.setState({
        caretHidden: true,
      });

    } else {

      this.setState({
        caretHidden: false,
      });

    }

<TextInput
        caretHidden = {this.state.caretHidden}
/>

    autoCompleteType='email'

Thank you @ahdzlee ! Saved me a lot of time searching for workarounds! Whew 👍

react-native: “0.61.5”, device:xiaomi MI 8 SE,android10,MIUI 11.0.3. In my project , it worked.Just add View in outside.Maybe you can try try.

<View>
        <TextInput
               keyboardType={'email-address'}
               multiline={false}
               placeholder={'请输入邮箱'}
               onChangeText={(inputData) => {
               }}/>
</View>

I think I found a workaround, just disable the scrolling of ScrollView when user starts editing, then everything will turn back to normal:

const [isScrollEnable, setIsScrollEnable] = useState(true)

return (
    <ScrollView scrollEnabled={isScrollEnable}>
        <TextInput
            onTouchStart={() => setIsScrollEnable(false)}
            onEndEditing={() => setIsScrollEnable(true)}
        />
    </ScrollView>
)

What about using react-native-device-info to check if it Xiaomi to enable caretHidden ?