react-native-wheel-picker icon indicating copy to clipboard operation
react-native-wheel-picker copied to clipboard

Android picker does not scroll back to desired index

Open samzmann opened this issue 5 years ago • 6 comments

I'm making a basic time picker with two wheels: hour, minutes. If the selected time is not valid, the wheel should turn back to the previously valid time.

For example, let's say it is 10:30am so valid times are 10:30am - 11:59pm. If the user selected 10:15, I expect the minute wheel to scroll back to 10:30.

This is working fine on iOS, but on Android, the wheel simply stays at the invalid index.

So my question is: how to enable programmatic scroll to an index of the picker on Android to achieve the same behavior as iOS?

Thanks :)

Env: react-native 0.57.8 react-native-wheel-picker 1.2.0

samzmann avatar Apr 29 '19 07:04 samzmann

So basically I guess the problem is the picker does not rerender when the props used for selectedValue have not changed. This is really weird because I see that the underlying <WheelCurvedPicker> props for selectedValue do change.

samzmann avatar May 02 '19 10:05 samzmann

I have a similar problem but in iOS. In fact, i'm doing the same input (HH:MM) with 2 scrolls. My problem is after scroll and select any hour or minute, the input is scroll down automatically to the initial value at the bottom (the value is updated in my local state, but after select, there is another scroll that i cannot manage, it is done without any interaction).

Any idea?

sebackend avatar May 24 '19 04:05 sebackend

@sebackend maybe you have some mistake in your onValueChange call, or how you call setState

Happy to help if u share code. However, I don't think this should be discussed in here as it sound like a somewhat different problem, plus its iOS and this issue thread is on Android only.

samzmann avatar May 27 '19 12:05 samzmann

So basically I guess the problem is the picker does not rerender when the props used for selectedValue have not changed. This is really weird because I see that the underlying <WheelCurvedPicker> props for selectedValue do change.

Hey @LaVielle I have kind of that issue on an Android device (ios works fine). Did you manage to resolve your issue?

riki-gertzik avatar Mar 22 '20 08:03 riki-gertzik

@riki-gertzik For us the solution was to only show valid data. So when the user mounts this picker wheel, we calculate valid times and generate an array to populate the picker. Then, when the user makes a selection, we check if the time is valid (slightly differently for iOS and Android). This is the function we use to handle picker selection, wrote almost a year a go so I don't fully understand it anymore, but hopefully you can get something out of it.

Note: the quickSelector you'll see bellow is some other UI element that allows user to pick relative time (eg. "in 15 mins"), and is not related to the time picker wheel

/**
 *   selectedPicker: {
 *     hour: String // "00" - "23"
 *     minute: String // "00" - "59"
 *     value: Moment // the full time/date for that valid time
 *   }
*/

handleTimePickerChange = (selectedPicker) => {
    const { validPickupTimesForDay, lastQuickSelectorReset, quickSelector } = this.state
    let { selectedHour, selectedMinute, selectedTime } = this.state

    // assumes we should reset quickSelector because iOS functions as expected:
    // onValueChange fires only on manual change
    let resetQuickSelector = true

    // 'debounce' quickSelector reset on Android:
    // onValueChange fires when Picker values are changed programmatically (not expected)
    // this happens only on Android, so we check if the last quickSelector change was long ago and reset it only then
    if (Platform.OS === 'android') {
      const now = moment().subtract(1500, 'ms')
      resetQuickSelector = quickSelector !== null && now.isAfter(lastQuickSelectorReset)
    }
    const validTime = this.checkValidity(validPickupTimesForDay, selectedPicker)
    if (validTime) {
      if (selectedPicker.type === 'hour') {
        selectedHour = selectedPicker
        selectedTime = validTime
      }
      if (selectedPicker.type === 'minute') {
        selectedMinute = selectedPicker
        selectedTime = validTime
      }
    } else if (selectedPicker.type === 'hour' && selectedPicker.index === 0) {
      // hour:minute combination is not valid, so reset minutes to soonest valid minute
      selectedMinute = {
        label: validPickupTimesForDay[0].minute,
        index: validPickupTimesForDay[0].value.get('minutes'),
      }
      selectedTime = this.getValidTime(validPickupTimesForDay, selectedPicker.label, selectedMinute.label)
      selectedHour = selectedPicker
    }
    this.setState({
      selectedHour,
      selectedMinute,
      selectedTime,
      quickSelector: resetQuickSelector ? null : quickSelector,
    })
  }

samzmann avatar Mar 23 '20 09:03 samzmann

@riki-gertzik For us the solution was to only show valid data. So when the user mounts this picker wheel, we calculate valid times and generate an array to populate the picker. Then, when the user makes a selection, we check if the time is valid (slightly differently for iOS and Android). This is the function we use to handle picker selection, wrote almost a year a go so I don't fully understand it anymore, but hopefully you can get something out of it.

Note: the quickSelector you'll see bellow is some other UI element that allows user to pick relative time (eg. "in 15 mins"), and is not related to the time picker wheel

/**
 *   selectedPicker: {
 *     hour: String // "00" - "23"
 *     minute: String // "00" - "59"
 *     value: Moment // the full time/date for that valid time
 *   }
*/

handleTimePickerChange = (selectedPicker) => {
    const { validPickupTimesForDay, lastQuickSelectorReset, quickSelector } = this.state
    let { selectedHour, selectedMinute, selectedTime } = this.state

    // assumes we should reset quickSelector because iOS functions as expected:
    // onValueChange fires only on manual change
    let resetQuickSelector = true

    // 'debounce' quickSelector reset on Android:
    // onValueChange fires when Picker values are changed programmatically (not expected)
    // this happens only on Android, so we check if the last quickSelector change was long ago and reset it only then
    if (Platform.OS === 'android') {
      const now = moment().subtract(1500, 'ms')
      resetQuickSelector = quickSelector !== null && now.isAfter(lastQuickSelectorReset)
    }
    const validTime = this.checkValidity(validPickupTimesForDay, selectedPicker)
    if (validTime) {
      if (selectedPicker.type === 'hour') {
        selectedHour = selectedPicker
        selectedTime = validTime
      }
      if (selectedPicker.type === 'minute') {
        selectedMinute = selectedPicker
        selectedTime = validTime
      }
    } else if (selectedPicker.type === 'hour' && selectedPicker.index === 0) {
      // hour:minute combination is not valid, so reset minutes to soonest valid minute
      selectedMinute = {
        label: validPickupTimesForDay[0].minute,
        index: validPickupTimesForDay[0].value.get('minutes'),
      }
      selectedTime = this.getValidTime(validPickupTimesForDay, selectedPicker.label, selectedMinute.label)
      selectedHour = selectedPicker
    }
    this.setState({
      selectedHour,
      selectedMinute,
      selectedTime,
      quickSelector: resetQuickSelector ? null : quickSelector,
    })
  }

thanks @LaVielle for your response, our problem is that we have one wheel for degrees, where you can switch between C and F but if the new item "value" is the same as the old one ("value" = index), I don't see the new items but the old items until I do some change - press or start scrolling. anyway, seems like a different problem.. I think it's related to when onValueChange called too but I'm not sure.

riki-gertzik avatar Mar 29 '20 06:03 riki-gertzik