Skip to content

[Android] Fix minDistance being reset by partial config updates - #4347

Merged
m-bert merged 7 commits into
mainfrom
@mbert/min-distance-reset
Jul 27, 2026
Merged

[Android] Fix minDistance being reset by partial config updates#4347
m-bert merged 7 commits into
mainfrom
@mbert/min-distance-reset

Conversation

@m-bert

@m-bert m-bert commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Description

Follow-up to #4327

On Android, Pan's updateConfig derived hasCustomActivationCriteria from a local variable recomputed from the keys present in the incoming config map. Config updates can be partial a SharedValue used in the config sends { [key]: value } through updateGestureHandlerConfig, which doesn't reset the handler first - so the flag reflected a single update message instead of the accumulated configuration. Any criteria-key update (velocity/offset) then hit the "custom criteria and no explicit minDist" branch and masked an explicitly configured minDistance with Float.MAX_VALUE, disabling distance-based activation entirely.

hasCustomActivationCriteria is now a field on the handler, together with hasExplicitMinDist tracking whether minDist was explicitly configured; both are cleared in resetConfig. This mirrors how the other platforms already model this state (web: instance field + stored minDist; iOS: flag recomputed from stored recognizer state in updateHasCustomActivationCriteria), which is why they don't exhibit the bug. Full-config paths (createGestureHandler / setGestureHandlerConfig) go through resetConfig first, so their behavior is unchanged.

Test plan

Tested on the following code:
import React, { useState } from 'react';
import { Button, StyleSheet, Text, View } from 'react-native';
import { GestureDetector, usePanGesture } from 'react-native-gesture-handler';
import { useSharedValue } from 'react-native-reanimated';

export default function MinDistReset() {
  const [log, setLog] = useState<string[]>([]);
  const minVelocityY = useSharedValue(10000);

  const append = (entry: string) =>
    setLog((prev) => [...prev.slice(-8), entry]);

  const pan = usePanGesture({
    minDistance: 50,
    minVelocityY,
    runOnJS: true,
    onActivate: () => {
      append('ACTIVATED');
    },
    onFinalize: (e) => {
      if (e.canceled) {
        append('finished without activation');
      }
    },
  });

  return (
    <View style={styles.container}>
      <Text style={styles.title}>
        minDistance = 50, minVelocityY = SharedValue(10000)
      </Text>
      <Text style={styles.hint}>
        Slow 50pt+ drag should ACTIVATE — also after poking the shared value.
      </Text>
      <Button
        title="poke shared value"
        onPress={() => {
          // Render-silent on purpose - see the note at the top of the file.
          minVelocityY.value = minVelocityY.value === 9000 ? 9001 : 9000;
          console.log('poked: minVelocityY ->', minVelocityY.value);
        }}
      />
      <GestureDetector gesture={pan}>
        <View style={styles.box} testID="panBox">
          <Text style={styles.boxLabel}>drag here</Text>
        </View>
      </GestureDetector>
      <View style={styles.log}>
        {log.map((entry, i) => (
          <Text key={i} style={styles.logLine}>
            {entry}
          </Text>
        ))}
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    paddingTop: 16,
  },
  title: {
    fontSize: 16,
    fontWeight: 'bold',
  },
  hint: {
    textAlign: 'center',
    marginVertical: 12,
    color: '#666',
  },
  box: {
    width: 300,
    height: 300,
    backgroundColor: 'tomato',
    borderRadius: 16,
    justifyContent: 'center',
    alignItems: 'center',
    marginTop: 12,
  },
  boxLabel: {
    color: 'white',
    fontSize: 18,
  },
  log: {
    marginTop: 20,
    minHeight: 160,
    alignSelf: 'stretch',
    paddingHorizontal: 24,
  },
  logLine: {
    fontFamily: 'monospace',
    fontSize: 13,
  },
});

Copilot AI review requested due to automatic review settings July 27, 2026 10:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes an Android-specific PanGestureHandler configuration bug where partial updateConfig calls (e.g., from SharedValue-driven updates) could incorrectly treat a single update payload as the full config, causing an explicitly set minDistance to be masked and distance-based activation to stop working.

Changes:

  • Persist hasCustomActivationCriteria across partial updateConfig calls to avoid recomputing state from only the incoming keys.
  • Track whether minDist was explicitly configured via a new hasExplicitMinDist flag.
  • Clear both flags in resetConfig to keep full-config paths (setGestureHandlerConfig / creation) behavior unchanged.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@m-bert
m-bert requested a review from j-piasecki July 27, 2026 10:38
@m-bert
m-bert merged commit 2de57ac into main Jul 27, 2026
3 checks passed
@m-bert
m-bert deleted the @mbert/min-distance-reset branch July 27, 2026 10:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants