From 6b6b5d2cfaf1b1ec988cb8e049b4b5c72a358c06 Mon Sep 17 00:00:00 2001 From: Vasu Nageshri Date: Sun, 26 Apr 2026 15:03:38 +0530 Subject: [PATCH] fix: onToggle fires when tapping already-active switch (#101) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When changeOnTap is true and doubleTapDisable is false, tapping the already-selected switch would incorrectly fire onToggle even though the selection did not change. Add an early-return guard in _handleOnTap to skip the callback when the tapped index equals the current initialLabelIndex. The existing doubleTapDisable behaviour is preserved: when that flag is true, tapping the active switch intentionally deselects it (index → null) and fires onToggle(null) as before. Fixes #101 --- lib/toggle_switch.dart | 10 ++++- test/toggle_switch_test.dart | 76 ++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/lib/toggle_switch.dart b/lib/toggle_switch.dart index 5529efb..b976f47 100644 --- a/lib/toggle_switch.dart +++ b/lib/toggle_switch.dart @@ -424,8 +424,16 @@ class _ToggleSwitchState extends State newIndex = null; } + // When the widget manages its own state and doubleTapDisable is false, + // skip onToggle if the tapped switch is already active — selection unchanged. + if (widget.changeOnTap && + !widget.doubleTapDisable && + widget.initialLabelIndex == newIndex) { + return; + } + final cancel = await widget.cancelToggle?.call(newIndex) ?? false; - if (cancel) { + if (!mounted || cancel) { return; } diff --git a/test/toggle_switch_test.dart b/test/toggle_switch_test.dart index ac41ff5..779d8bc 100644 --- a/test/toggle_switch_test.dart +++ b/test/toggle_switch_test.dart @@ -283,4 +283,80 @@ void main() { expect(helloTextFinder, findsOneWidget); expect(flutterTextFinder, findsOneWidget); }); + + // onToggle should NOT be called when tapping the already-active switch + // when changeOnTap is true and doubleTapDisable is false. + testWidgets('onToggle is not triggered when tapping already-active switch', + (WidgetTester tester) async { + int toggleCallCount = 0; + + await tester.pumpWidget( + MediaQuery( + data: MediaQueryData(size: const Size(800, 600)), + child: MaterialApp( + home: Scaffold( + body: Center( + child: ToggleSwitch( + totalSwitches: 3, + labels: ['A', 'B', 'C'], + initialLabelIndex: 0, + onToggle: (index) { + toggleCallCount++; + }, + ), + ), + ), + ), + ), + ); + + // Tap on the already-active switch (label 'A', index 0). + await tester.tap(find.text('A')); + await tester.pumpAndSettle(); + + // onToggle should NOT have been called. + expect(toggleCallCount, equals(0)); + + // Tap on a different switch (label 'B', index 1) — should fire onToggle. + await tester.tap(find.text('B')); + await tester.pumpAndSettle(); + + expect(toggleCallCount, equals(1)); + }); + + // When doubleTapDisable is true, tapping the active switch should fire + // onToggle with null (deselect), not be swallowed. + testWidgets( + 'onToggle fires with null when doubleTapDisable is true and active switch is tapped', + (WidgetTester tester) async { + int? lastToggledIndex = -1; + + await tester.pumpWidget( + MediaQuery( + data: MediaQueryData(size: const Size(800, 600)), + child: MaterialApp( + home: Scaffold( + body: Center( + child: ToggleSwitch( + totalSwitches: 3, + labels: ['A', 'B', 'C'], + initialLabelIndex: 0, + doubleTapDisable: true, + onToggle: (index) { + lastToggledIndex = index; + }, + ), + ), + ), + ), + ), + ); + + // Tap on the already-active switch (label 'A', index 0). + await tester.tap(find.text('A')); + await tester.pumpAndSettle(); + + // onToggle should be called with null (deselect). + expect(lastToggledIndex, isNull); + }); }