Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Picker.onValueChange on Android sometimes not fired due to race condition #22821

Closed
wants to merge 1 commit into from

Conversation

Kudo
Copy link
Contributor

@Kudo Kudo commented Dec 28, 2018

Changelog:

[Android] [Fixed] - Fix Picker.onValueChange sometimes not fired due to race condition. Fixes #15556

Root Cause:

Please check my code snippet at https://snack.expo.io/@kudochien/android-picker-issue
and try to select different items on Picker to see if console.log() hit.
If calling setState() with some latency, e.g. setTimeout() or changes from redux,
the second time changing picker item on UI, the onValueChange() will be not fired.

The root cause comes from the forceUpdate in PickerAndroid.android.js.
If user's setState() update comes after forceUpdate(), the flow will be:

  1. First time select picker item
  2. onValueChange + forceUpdate
  3. user's setState() + componentDidUpdate + setNativeProps({ selected: ... })
  4. mSuppressNextEvent = true
  5. Second time select picker item
  6. Since mSuppressNextEvent is true, the onValueChange will not be fired.

Solution:

Like other controlled components, disable change listener during setup values from JS.
Android Spinner setSelection(int position) is asynchronous call, i.e. will fire onItemSelected in next run loop and is not suitable for us.
setSelection(int position, boolean animate), however, is synchronous call which I used.

Some more references about setSelection:
https://stackoverflow.com/a/43512925/2590265
http://androidxref.com/8.1.0_r33/xref/frameworks/base/core/java/android/widget/AbsSpinner.java#276
The two arguments version will use setSelectionInt() which set mBlockLayoutRequests as true to prevent onItemSelected call from next layout().

I also moved the setOnItemSelectedListener() call after onLayout to prevent onValueChange() during intialization.

Test Plan:

  1. Test PickerExample in RNTester
  2. Test my test cases at https://snack.expo.io/@kudochien/android-picker-issue with and without setTimeout()
  3. I also removed some FlowFixMe, tested with this command to ensure no flow errors:
yarn flow start --flowconfig-name .flowconfig.android
yarn flow status --flowconfig-name .flowconfig.android

@facebook-github-bot facebook-github-bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Dec 28, 2018
@cpojer
Copy link
Contributor

cpojer commented Jan 18, 2019

Thanks for this PR and the extensive test plan. Fingers crossed that it is gonna cover all cases and won't cause weird issues but your code looks solid :)

@facebook-github-bot facebook-github-bot added the Import Started This pull request has been imported. This does not imply the PR has been approved. label Jan 18, 2019
Copy link
Contributor

@facebook-github-bot facebook-github-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cpojer is landing this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

@react-native-bot
Copy link
Collaborator

@Kudo merged commit 6a3d9c0 into facebook:master.

@facebook facebook locked as resolved and limited conversation to collaborators Jan 18, 2019
@react-native-bot react-native-bot added the Merged This PR has been merged. label Jan 18, 2019
@hramos hramos removed Import Started This pull request has been imported. This does not imply the PR has been approved. labels Feb 6, 2019
matt-oakes pushed a commit to matt-oakes/react-native that referenced this pull request Feb 7, 2019
…ondition (facebook#22821)

Summary:
Changelog:
----------

[Android] [Fixed] - Fix Picker.onValueChange sometimes not fired due to race condition. Fixes facebook#15556

Root Cause:
------------

Please check my code snippet at https://snack.expo.io/kudochien/android-picker-issue
and try to select different items on Picker to see if console.log() hit.
If calling setState() with some latency, e.g. setTimeout() or changes from redux,
the second time changing picker item on UI, the onValueChange() will be not fired.

The root cause comes from the `forceUpdate` in PickerAndroid.android.js.
If user's setState() update comes after forceUpdate(), the flow will be:
1. First time select picker item
2. onValueChange + forceUpdate
3. user's setState() + componentDidUpdate + setNativeProps({ selected: ... })
4. mSuppressNextEvent = true
5. Second time select picker item
6. Since mSuppressNextEvent is true, the onValueChange will not be fired.

Solution:
---------

Like other controlled components, disable change listener during setup values from JS.
Android Spinner `setSelection(int position)` is asynchronous call, i.e. will fire onItemSelected in next run loop and is not suitable for us.
`setSelection(int position, boolean animate)`, however, is synchronous call which I used.

Some more references about setSelection:
https://stackoverflow.com/a/43512925/2590265
http://androidxref.com/8.1.0_r33/xref/frameworks/base/core/java/android/widget/AbsSpinner.java#276
The two arguments version will use `setSelectionInt()` which set mBlockLayoutRequests as true to prevent onItemSelected call from next layout().

I also moved the setOnItemSelectedListener() call after onLayout to prevent onValueChange() during intialization.
Pull Request resolved: facebook#22821

Differential Revision: D13731979

Pulled By: cpojer

fbshipit-source-id: e06bd9aa62463b66c8f3fd7214485898d5375054
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Merged This PR has been merged. Platform: Android Android applications.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants