diff --git a/packages/flutter/lib/src/gestures/drag.dart b/packages/flutter/lib/src/gestures/drag.dart index 4518ac2d1b..befd77acfa 100644 --- a/packages/flutter/lib/src/gestures/drag.dart +++ b/packages/flutter/lib/src/gestures/drag.dart @@ -182,7 +182,7 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { _initialPosition = event.position; _pendingDragOffset = Offset.zero; if (onDown != null) - onDown(new DragDownDetails(globalPosition: _initialPosition)); + invokeCallback/**/('onDown', () => onDown(new DragDownDetails(globalPosition: _initialPosition))); } } @@ -196,11 +196,11 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { Offset delta = event.delta; if (_state == _DragState.accepted) { if (onUpdate != null) { - onUpdate(new DragUpdateDetails( + invokeCallback/**/('onUpdate', () => onUpdate(new DragUpdateDetails( delta: _getDeltaForDetails(delta), primaryDelta: _getPrimaryDeltaForDetails(delta), globalPosition: event.position - )); + ))); } } else { _pendingDragOffset += delta; @@ -218,12 +218,12 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { Offset delta = _pendingDragOffset; _pendingDragOffset = Offset.zero; if (onStart != null) - onStart(new DragStartDetails(globalPosition: _initialPosition)); + invokeCallback/**/('onStart', () => onStart(new DragStartDetails(globalPosition: _initialPosition))); if (delta != Offset.zero && onUpdate != null) { - onUpdate(new DragUpdateDetails( + invokeCallback/**/('onUpdate', () => onUpdate(new DragUpdateDetails( delta: _getDeltaForDetails(delta), primaryDelta: _getPrimaryDeltaForDetails(delta) - )); + ))); } } } @@ -239,7 +239,7 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { resolve(GestureDisposition.rejected); _state = _DragState.ready; if (onCancel != null) - onCancel(); + invokeCallback/**/('onCancel', onCancel); return; } bool wasAccepted = (_state == _DragState.accepted); @@ -253,9 +253,9 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { final Offset pixelsPerSecond = velocity.pixelsPerSecond; if (pixelsPerSecond.distanceSquared > kMaxFlingVelocity * kMaxFlingVelocity) velocity = new Velocity(pixelsPerSecond: (pixelsPerSecond / pixelsPerSecond.distance) * kMaxFlingVelocity); - onEnd(new DragEndDetails(velocity: velocity)); + invokeCallback/**/('onEnd', () => onEnd(new DragEndDetails(velocity: velocity))); } else { - onEnd(new DragEndDetails(velocity: Velocity.zero)); + invokeCallback/**/('onEnd', () => onEnd(new DragEndDetails(velocity: Velocity.zero))); } } _velocityTrackers.clear(); diff --git a/packages/flutter/lib/src/gestures/long_press.dart b/packages/flutter/lib/src/gestures/long_press.dart index 27ba6e4a45..25685b6193 100644 --- a/packages/flutter/lib/src/gestures/long_press.dart +++ b/packages/flutter/lib/src/gestures/long_press.dart @@ -26,7 +26,7 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { void didExceedDeadline() { resolve(GestureDisposition.accepted); if (onLongPress != null) - onLongPress(); + invokeCallback/**/('onLongPress', onLongPress); } @override diff --git a/packages/flutter/lib/src/gestures/multidrag.dart b/packages/flutter/lib/src/gestures/multidrag.dart index f1b1aaa55b..86eca4c668 100644 --- a/packages/flutter/lib/src/gestures/multidrag.dart +++ b/packages/flutter/lib/src/gestures/multidrag.dart @@ -255,7 +255,7 @@ abstract class MultiDragGestureRecognizer exten assert(state._pendingDelta != null); Drag drag; if (onStart != null) - drag = onStart(initialPosition); + drag = invokeCallback/**/('onStart', () => onStart(initialPosition)); if (drag != null) { state._startDrag(drag); } else { diff --git a/packages/flutter/lib/src/gestures/multitap.dart b/packages/flutter/lib/src/gestures/multitap.dart index 738a87127b..5b2d6d94fc 100644 --- a/packages/flutter/lib/src/gestures/multitap.dart +++ b/packages/flutter/lib/src/gestures/multitap.dart @@ -191,7 +191,7 @@ class DoubleTapGestureRecognizer extends GestureRecognizer { _freezeTracker(tracker); _trackers.remove(tracker.pointer); if (onDoubleTap != null) - onDoubleTap(); + invokeCallback/**/('onDoubleTap', onDoubleTap); _reset(); } @@ -359,7 +359,7 @@ class MultiTapGestureRecognizer extends GestureRecognizer { longTapDelay: longTapDelay ); if (onTapDown != null) - onTapDown(event.pointer, new TapDownDetails(globalPosition: event.position)); + invokeCallback/**/('onTapDown', () => onTapDown(event.pointer, new TapDownDetails(globalPosition: event.position))); } @override @@ -380,19 +380,19 @@ class MultiTapGestureRecognizer extends GestureRecognizer { _gestureMap.remove(pointer); if (resolution == _TapResolution.tap) { if (onTapUp != null) - onTapUp(pointer, new TapUpDetails(globalPosition: globalPosition)); + invokeCallback/**/('onTapUp', () => onTapUp(pointer, new TapUpDetails(globalPosition: globalPosition))); if (onTap != null) - onTap(pointer); + invokeCallback/**/('onTap', () => onTap(pointer)); } else { if (onTapCancel != null) - onTapCancel(pointer); + invokeCallback/**/('onTapCancel', () => onTapCancel(pointer)); } } void _handleLongTap(int pointer, Point lastPosition) { assert(_gestureMap.containsKey(pointer)); if (onLongTapDown != null) - onLongTapDown(pointer, new TapDownDetails(globalPosition: lastPosition)); + invokeCallback/**/('onLongTapDown', () => onLongTapDown(pointer, new TapDownDetails(globalPosition: lastPosition))); } @override diff --git a/packages/flutter/lib/src/gestures/recognizer.dart b/packages/flutter/lib/src/gestures/recognizer.dart index b354c2cb28..65e7d8797f 100644 --- a/packages/flutter/lib/src/gestures/recognizer.dart +++ b/packages/flutter/lib/src/gestures/recognizer.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'dart:collection'; import 'dart:ui' show Point, Offset; +import 'package:flutter/foundation.dart'; import 'package:meta/meta.dart'; import 'arena.dart'; @@ -16,6 +17,8 @@ import 'pointer_router.dart'; export 'pointer_router.dart' show PointerRouter; +typedef T RecognizerCallback(); + /// The base class that all GestureRecognizers should inherit from. /// /// Provides a basic API that can be used by classes that work with @@ -48,6 +51,28 @@ abstract class GestureRecognizer extends GestureArenaMember { /// Returns a very short pretty description of the gesture that the /// recognizer looks for, like 'tap' or 'horizontal drag'. String toStringShort() => toString(); + + /// Invoke a callback provided by the application and log any exceptions. + @protected + dynamic/*=T*/ invokeCallback/**/(String name, RecognizerCallback callback) { + dynamic/*=T*/ result; + try { + result = callback(); + } catch (exception, stack) { + FlutterError.reportError(new FlutterErrorDetails( + exception: exception, + stack: stack, + library: 'gesture', + context: 'while handling a gesture', + informationCollector: (StringBuffer information) { + information.writeln('Handler: $name'); + information.writeln('Recognizer:'); + information.writeln(' $this'); + } + )); + } + return result; + } } /// Base class for gesture recognizers that can only recognize one diff --git a/packages/flutter/lib/src/gestures/scale.dart b/packages/flutter/lib/src/gestures/scale.dart index fc0faaf584..2ba4f53ea2 100644 --- a/packages/flutter/lib/src/gestures/scale.dart +++ b/packages/flutter/lib/src/gestures/scale.dart @@ -180,9 +180,9 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer { final Offset pixelsPerSecond = velocity.pixelsPerSecond; if (pixelsPerSecond.distanceSquared > kMaxFlingVelocity * kMaxFlingVelocity) velocity = new Velocity(pixelsPerSecond: (pixelsPerSecond / pixelsPerSecond.distance) * kMaxFlingVelocity); - onEnd(new ScaleEndDetails(velocity: velocity)); + invokeCallback/**/('onEnd', () => onEnd(new ScaleEndDetails(velocity: velocity))); } else { - onEnd(new ScaleEndDetails(velocity: Velocity.zero)); + invokeCallback/**/('onEnd', () => onEnd(new ScaleEndDetails(velocity: Velocity.zero))); } } _state = ScaleState.accepted; @@ -200,11 +200,11 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer { if (_state == ScaleState.accepted && !configChanged) { _state = ScaleState.started; if (onStart != null) - onStart(new ScaleStartDetails(focalPoint: focalPoint)); + invokeCallback/**/('onStart', () => onStart(new ScaleStartDetails(focalPoint: focalPoint))); } if (_state == ScaleState.started && onUpdate != null) - onUpdate(new ScaleUpdateDetails(scale: _scaleFactor, focalPoint: focalPoint)); + invokeCallback/**/('onUpdate', () => onUpdate(new ScaleUpdateDetails(scale: _scaleFactor, focalPoint: focalPoint))); } @override diff --git a/packages/flutter/lib/src/gestures/tap.dart b/packages/flutter/lib/src/gestures/tap.dart index 164c710a86..3d1ebe8f29 100644 --- a/packages/flutter/lib/src/gestures/tap.dart +++ b/packages/flutter/lib/src/gestures/tap.dart @@ -99,7 +99,7 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer { void resolve(GestureDisposition disposition) { if (_wonArenaForPrimaryPointer && disposition == GestureDisposition.rejected) { if (onTapCancel != null) - onTapCancel(); + invokeCallback/**/('onTapCancel', onTapCancel); _reset(); } super.resolve(disposition); @@ -126,7 +126,7 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer { if (pointer == primaryPointer) { assert(state == GestureRecognizerState.defunct); if (onTapCancel != null) - onTapCancel(); + invokeCallback/**/('onTapCancel', onTapCancel); _reset(); } } @@ -134,7 +134,7 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer { void _checkDown() { if (!_sentTapDown) { if (onTapDown != null) - onTapDown(new TapDownDetails(globalPosition: initialPosition)); + invokeCallback/**/('onTapDown', () => onTapDown(new TapDownDetails(globalPosition: initialPosition))); _sentTapDown = true; } } @@ -143,9 +143,9 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer { if (_wonArenaForPrimaryPointer && _finalPosition != null) { resolve(GestureDisposition.accepted); if (onTapUp != null) - onTapUp(new TapUpDetails(globalPosition: _finalPosition)); + invokeCallback/**/('onTapUp', () => onTapUp(new TapUpDetails(globalPosition: _finalPosition))); if (onTap != null) - onTap(); + invokeCallback/**/('onTap', onTap); _reset(); } } diff --git a/packages/flutter/test/gestures/tap_test.dart b/packages/flutter/test/gestures/tap_test.dart index 966767eb98..517c608a8e 100644 --- a/packages/flutter/test/gestures/tap_test.dart +++ b/packages/flutter/test/gestures/tap_test.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:test/test.dart'; @@ -259,4 +260,28 @@ void main() { tap.dispose(); }); + testGesture('Should log exceptions from callbacks', (GestureTester tester) { + TapGestureRecognizer tap = new TapGestureRecognizer(); + + tap.onTap = () { + throw new Exception(test); + }; + + FlutterExceptionHandler previousErrorHandler = FlutterError.onError; + bool gotError = false; + FlutterError.onError = (FlutterErrorDetails details) { + gotError = true; + }; + + tap.addPointer(down1); + tester.closeArena(1); + tester.route(down1); + expect(gotError, isFalse); + + tester.route(up1); + expect(gotError, isTrue); + + FlutterError.onError = previousErrorHandler; + tap.dispose(); + }); }