[flutter_releases] Flutter Framework 1.26.0-17.5.pre Beta Cherrypicks (#75804)
* Added a ReorderableListView.builder constructor (#74697)
Added constructor parameters to the ReorderableList that were missing from ScrollView. Also replace a lot of doc comments with macro templates to reduce duplication.
* [flutter_tools] handle further devtools NPE (#74764)
* [flutter_tools] skip web renderer defines unless web target is picked (#75160)
* Catch VM Service disappearance from run/attach handler code (#75298)
* [flutter_tools] handle null package (#75336)
* Return an empty FlutterViews list when the service disappears (#75301)
* fix analyzer
* Apply Engine Cherrypicks
Co-authored-by: Darren Austin <darrenaustin@google.com>
Co-authored-by: Jonah Williams <jonahwilliams@google.com>
Co-authored-by: Jenn Magder <magder@google.com>
diff --git a/bin/internal/engine.version b/bin/internal/engine.version
index ccceb04..190b5ff 100644
--- a/bin/internal/engine.version
+++ b/bin/internal/engine.version
@@ -1 +1 @@
-2c527d6c7e70e2f51bca1a46f1174b250f84c5da
+d4453f601890ec682bbf8f5659b70f15cce1d67d
diff --git a/packages/flutter/lib/src/material/reorderable_list.dart b/packages/flutter/lib/src/material/reorderable_list.dart
index 0b87797..905bcbd 100644
--- a/packages/flutter/lib/src/material/reorderable_list.dart
+++ b/packages/flutter/lib/src/material/reorderable_list.dart
@@ -4,6 +4,7 @@
import 'dart:ui' show lerpDouble;
+import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
@@ -20,7 +21,7 @@
/// child that could possibly be displayed in the list view instead of just
/// those children that are actually visible.
///
-/// All [children] must have a key.
+/// All list items must have a key.
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=3fB1mxOsqJE}
///
@@ -64,18 +65,32 @@
///
///{@end-tool}
class ReorderableListView extends StatefulWidget {
- /// Creates a reorderable list.
+ /// Creates a reorderable list from a pre-built list of widgets.
+ ///
+ /// See also:
+ ///
+ /// * [ReorderableListView.builder], which allows you to build a reorderable
+ /// list where the items are built as needed when scrolling the list.
ReorderableListView({
Key? key,
- this.header,
- required this.children,
+ required List<Widget> children,
required this.onReorder,
- this.scrollController,
- this.scrollDirection = Axis.vertical,
- this.padding,
- this.reverse = false,
- this.buildDefaultDragHandles = true,
this.proxyDecorator,
+ this.buildDefaultDragHandles = true,
+ this.padding,
+ this.header,
+ this.scrollDirection = Axis.vertical,
+ this.reverse = false,
+ this.scrollController,
+ this.primary,
+ this.physics,
+ this.shrinkWrap = false,
+ this.anchor = 0.0,
+ this.cacheExtent,
+ this.dragStartBehavior = DragStartBehavior.start,
+ this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
+ this.restorationId,
+ this.clipBehavior = Clip.hardEdge,
}) : assert(scrollDirection != null),
assert(onReorder != null),
assert(children != null),
@@ -84,54 +99,70 @@
'All children of this widget must have a key.',
),
assert(buildDefaultDragHandles != null),
+ itemBuilder = ((BuildContext context, int index) => children[index]),
+ itemCount = children.length,
super(key: key);
- /// A non-reorderable header widget to show before the list.
+ /// Creates a reorderable list from widget items that are created on demand.
///
- /// If null, no header will appear before the list.
- final Widget? header;
+ /// This constructor is appropriate for list views with a large number of
+ /// children because the builder is called only for those children
+ /// that are actually visible.
+ ///
+ /// The `itemBuilder` callback will be called only with indices greater than
+ /// or equal to zero and less than `itemCount`.
+ ///
+ /// The `itemBuilder` should always return a non-null widget, and actually
+ /// create the widget instances when called. Avoid using a builder that
+ /// returns a previously-constructed widget; if the list view's children are
+ /// created in advance, or all at once when the [ReorderableListView] itself
+ /// is created, it is more efficient to use the [ReorderableListView]
+ /// constructor. Even more efficient, however, is to create the instances
+ /// on demand using this constructor's `itemBuilder` callback.
+ ///
+ /// See also:
+ ///
+ /// * [ReorderableListView], which allows you to build a reorderable
+ /// list with all the items passed into the constructor.
+ const ReorderableListView.builder({
+ Key? key,
+ required this.itemBuilder,
+ required this.itemCount,
+ required this.onReorder,
+ this.proxyDecorator,
+ this.buildDefaultDragHandles = true,
+ this.padding,
+ this.header,
+ this.scrollDirection = Axis.vertical,
+ this.reverse = false,
+ this.scrollController,
+ this.primary,
+ this.physics,
+ this.shrinkWrap = false,
+ this.anchor = 0.0,
+ this.cacheExtent,
+ this.dragStartBehavior = DragStartBehavior.start,
+ this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
+ this.restorationId,
+ this.clipBehavior = Clip.hardEdge,
+ }) : assert(scrollDirection != null),
+ assert(itemCount >= 0),
+ assert(onReorder != null),
+ assert(buildDefaultDragHandles != null),
+ super(key: key);
- /// The widgets to display.
- final List<Widget> children;
+ /// {@macro flutter.widgets.reorderable_list.itemBuilder}
+ final IndexedWidgetBuilder itemBuilder;
- /// The [Axis] along which the list scrolls.
- ///
- /// List [children] can only drag along this [Axis].
- final Axis scrollDirection;
+ /// {@macro flutter.widgets.reorderable_list.itemCount}
+ final int itemCount;
- /// Creates a [ScrollPosition] to manage and determine which portion
- /// of the content is visible in the scroll view.
- ///
- /// This can be used in many ways, such as setting an initial scroll offset,
- /// (via [ScrollController.initialScrollOffset]), reading the current scroll position
- /// (via [ScrollController.offset]), or changing it (via [ScrollController.jumpTo] or
- /// [ScrollController.animateTo]).
- final ScrollController? scrollController;
-
- /// The amount of space by which to inset the [children].
- final EdgeInsets? padding;
-
- /// Whether the scroll view scrolls in the reading direction.
- ///
- /// For example, if the reading direction is left-to-right and
- /// [scrollDirection] is [Axis.horizontal], then the scroll view scrolls from
- /// left to right when [reverse] is false and from right to left when
- /// [reverse] is true.
- ///
- /// Similarly, if [scrollDirection] is [Axis.vertical], then the scroll view
- /// scrolls from top to bottom when [reverse] is false and from bottom to top
- /// when [reverse] is true.
- ///
- /// Defaults to false.
- final bool reverse;
-
- /// Called when a list child is dropped into a new position to shuffle the
- /// underlying list.
- ///
- /// This [ReorderableListView] calls [onReorder] after a list child is dropped
- /// into a new position.
+ /// {@macro flutter.widgets.reorderable_list.onReorder}
final ReorderCallback onReorder;
+ /// {@macro flutter.widgets.reorderable_list.proxyDecorator}
+ final ReorderItemProxyDecorator? proxyDecorator;
+
/// If true: on desktop platforms, a drag handle is stacked over the
/// center of each item's trailing edge; on mobile platforms, a long
/// press anywhere on the item starts a drag.
@@ -201,12 +232,56 @@
///{@end-tool}
final bool buildDefaultDragHandles;
- /// A callback that allows the app to add an animated decoration around
- /// an item when it is being dragged.
+ /// {@macro flutter.widgets.reorderable_list.padding}
+ final EdgeInsets? padding;
+
+ /// A non-reorderable header item to show before the items of the list.
///
- /// If this is null, a default decoration of a Material widget with
- /// an animated elevation will be used.
- final ReorderItemProxyDecorator? proxyDecorator;
+ /// If null, no header will appear before the list.
+ final Widget? header;
+
+ /// {@macro flutter.widgets.scroll_view.scrollDirection}
+ final Axis scrollDirection;
+
+ /// {@macro flutter.widgets.scroll_view.reverse}
+ final bool reverse;
+
+ /// {@macro flutter.widgets.scroll_view.controller}
+ final ScrollController? scrollController;
+
+ /// {@macro flutter.widgets.scroll_view.primary}
+
+ /// Defaults to true when [scrollDirection] is [Axis.vertical] and
+ /// [scrollController] is null.
+ final bool? primary;
+
+ /// {@macro flutter.widgets.scroll_view.physics}
+ final ScrollPhysics? physics;
+
+ /// {@macro flutter.widgets.scroll_view.shrinkWrap}
+ final bool shrinkWrap;
+
+ /// {@macro flutter.widgets.scroll_view.anchor}
+ final double anchor;
+
+ /// {@macro flutter.rendering.RenderViewportBase.cacheExtent}
+ final double? cacheExtent;
+
+ /// {@macro flutter.widgets.scrollable.dragStartBehavior}
+ final DragStartBehavior dragStartBehavior;
+
+ /// {@macro flutter.widgets.scroll_view.keyboardDismissBehavior}
+ ///
+ /// The default is [ScrollViewKeyboardDismissBehavior.manual]
+ final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;
+
+ /// {@macro flutter.widgets.scrollable.restorationId}
+ final String? restorationId;
+
+ /// {@macro flutter.material.Material.clipBehavior}
+ ///
+ /// Defaults to [Clip.hardEdge].
+ final Clip clipBehavior;
@override
_ReorderableListViewState createState() => _ReorderableListViewState();
@@ -230,15 +305,25 @@
opaque: true,
builder: (BuildContext context) {
return _ReorderableListContent(
- header: widget.header,
- children: widget.children,
- scrollController: widget.scrollController,
- scrollDirection: widget.scrollDirection,
- padding: widget.padding,
+ itemBuilder: widget.itemBuilder,
+ itemCount: widget.itemCount,
onReorder: widget.onReorder,
- reverse: widget.reverse,
- buildDefaultDragHandles: widget.buildDefaultDragHandles,
proxyDecorator: widget.proxyDecorator,
+ buildDefaultDragHandles: widget.buildDefaultDragHandles,
+ padding: widget.padding,
+ header: widget.header,
+ scrollDirection: widget.scrollDirection,
+ reverse: widget.reverse,
+ scrollController: widget.scrollController,
+ primary: widget.primary,
+ physics: widget.physics,
+ shrinkWrap: widget.shrinkWrap,
+ anchor: widget.anchor,
+ cacheExtent: widget.cacheExtent,
+ dragStartBehavior: widget.dragStartBehavior,
+ keyboardDismissBehavior: widget.keyboardDismissBehavior,
+ restorationId: widget.restorationId,
+ clipBehavior: widget.clipBehavior,
);
},
);
@@ -265,26 +350,46 @@
class _ReorderableListContent extends StatefulWidget {
const _ReorderableListContent({
- required this.header,
- required this.children,
- required this.scrollController,
- required this.scrollDirection,
- required this.padding,
+ required this.itemBuilder,
+ required this.itemCount,
required this.onReorder,
- required this.reverse,
- required this.buildDefaultDragHandles,
required this.proxyDecorator,
+ required this.buildDefaultDragHandles,
+ required this.padding,
+ required this.header,
+ required this.scrollDirection,
+ required this.reverse,
+ required this.scrollController,
+ required this.primary,
+ required this.physics,
+ required this.shrinkWrap,
+ required this.anchor,
+ required this.cacheExtent,
+ required this.dragStartBehavior,
+ required this.keyboardDismissBehavior,
+ required this.restorationId,
+ required this.clipBehavior,
});
- final Widget? header;
- final List<Widget> children;
- final ScrollController? scrollController;
- final Axis scrollDirection;
- final EdgeInsets? padding;
+ final IndexedWidgetBuilder itemBuilder;
+ final int itemCount;
final ReorderCallback onReorder;
- final bool reverse;
- final bool buildDefaultDragHandles;
final ReorderItemProxyDecorator? proxyDecorator;
+ final bool buildDefaultDragHandles;
+ final EdgeInsets? padding;
+ final Widget? header;
+ final Axis scrollDirection;
+ final bool reverse;
+ final ScrollController? scrollController;
+ final bool? primary;
+ final ScrollPhysics? physics;
+ final bool shrinkWrap;
+ final double anchor;
+ final double? cacheExtent;
+ final DragStartBehavior dragStartBehavior;
+ final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;
+ final String? restorationId;
+ final Clip clipBehavior;
@override
_ReorderableListContentState createState() => _ReorderableListContentState();
@@ -302,7 +407,7 @@
// Create the appropriate semantics actions.
void moveToStart() => reorder(index, 0);
- void moveToEnd() => reorder(index, widget.children.length);
+ void moveToEnd() => reorder(index, widget.itemCount);
void moveBefore() => reorder(index, index - 1);
// To move after, we go to index+2 because we are moving it to the space
// before index+2, which is after the space at index+1.
@@ -323,7 +428,7 @@
}
// If the item can move to after its current position in the list.
- if (index < widget.children.length - 1) {
+ if (index < widget.itemCount - 1) {
String reorderItemAfter = localizations.reorderItemDown;
if (widget.scrollDirection == Axis.horizontal) {
reorderItemAfter = Directionality.of(context) == TextDirection.ltr
@@ -349,7 +454,7 @@
}
Widget _itemBuilder(BuildContext context, int index) {
- final Widget item = widget.children[index];
+ final Widget item = widget.itemBuilder(context, index);
// TODO(goderbauer): The semantics stuff should probably happen inside
// _ReorderableItem so the widget versions can have them as well.
@@ -450,6 +555,15 @@
scrollDirection: widget.scrollDirection,
reverse: widget.reverse,
controller: widget.scrollController,
+ primary: widget.primary,
+ physics: widget.physics,
+ shrinkWrap: widget.shrinkWrap,
+ anchor: widget.anchor,
+ cacheExtent: widget.cacheExtent,
+ dragStartBehavior: widget.dragStartBehavior,
+ keyboardDismissBehavior: widget.keyboardDismissBehavior,
+ restorationId: widget.restorationId,
+ clipBehavior: widget.clipBehavior,
slivers: <Widget>[
if (widget.header != null)
SliverToBoxAdapter(child: widget.header!),
@@ -457,7 +571,7 @@
padding: listPadding,
sliver: SliverReorderableList(
itemBuilder: _itemBuilder,
- itemCount: widget.children.length,
+ itemCount: widget.itemCount,
onReorder: widget.onReorder,
proxyDecorator: widget.proxyDecorator ?? _proxyDecorator,
),
diff --git a/packages/flutter/lib/src/widgets/reorderable_list.dart b/packages/flutter/lib/src/widgets/reorderable_list.dart
index cbba955..e47ea0e 100644
--- a/packages/flutter/lib/src/widgets/reorderable_list.dart
+++ b/packages/flutter/lib/src/widgets/reorderable_list.dart
@@ -68,10 +68,10 @@
/// The [child] will be the item that is being dragged, and [index] is the
/// position of the item in the list.
///
-/// The [animation] will be driven from 0 to 1.0 while the item is being picked
-/// up during a drag operation, and reversed from 1.0 to 0 when the item is
-/// dropped. This can be used to animate properties of the proxy like an
-/// elevation or border.
+/// The [animation] will be driven forward from 0.0 to 1.0 while the item is
+/// being picked up during a drag operation, and reversed from 1.0 to 0.0 when
+/// the item is dropped. This can be used to animate properties of the proxy
+/// like an elevation or border.
///
/// The returned value will typically be the [child] wrapped in other widgets.
typedef ReorderItemProxyDecorator = Widget Function(Widget child, int index, Animation<double> animation);
@@ -113,16 +113,23 @@
required this.itemCount,
required this.onReorder,
this.proxyDecorator,
+ this.padding,
this.scrollDirection = Axis.vertical,
this.reverse = false,
this.controller,
this.primary,
this.physics,
this.shrinkWrap = false,
- this.padding,
+ this.anchor = 0.0,
+ this.cacheExtent,
+ this.dragStartBehavior = DragStartBehavior.start,
+ this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
+ this.restorationId,
+ this.clipBehavior = Clip.hardEdge,
}) : assert(itemCount >= 0),
super(key: key);
+ /// {@template flutter.widgets.reorderable_list.itemBuilder}
/// Called, as needed, to build list item widgets.
///
/// List items are only built when they're scrolled into view.
@@ -133,91 +140,76 @@
/// unique [Key], and should have some kind of listener to start the drag
/// (usually a [ReorderableDragStartListener] or
/// [ReorderableDelayedDragStartListener]).
+ /// {@endtemplate}
final IndexedWidgetBuilder itemBuilder;
+ /// {@template flutter.widgets.reorderable_list.itemCount}
/// The number of items in the list.
///
/// It must be a non-negative integer. When zero, nothing is displayed and
/// the widget occupies no space.
+ /// {@endtemplate}
final int itemCount;
+ /// {@template flutter.widgets.reorderable_list.onReorder}
/// A callback used by the list to report that a list item has been dragged
/// to a new location in the list and the application should update the order
/// of the items.
+ /// {@endtemplate}
final ReorderCallback onReorder;
+ /// {@template flutter.widgets.reorderable_list.proxyDecorator}
/// A callback that allows the app to add an animated decoration around
/// an item when it is being dragged.
+ /// {@endtemplate}
final ReorderItemProxyDecorator? proxyDecorator;
- /// The axis along which the list of items scrolls.
+ /// {@template flutter.widgets.reorderable_list.padding}
+ /// The amount of space by which to inset the list contents.
///
- /// Defaults to [Axis.vertical].
+ /// It defaults to `EdgeInsets.all(0)`.
+ /// {@endtemplate}
+ final EdgeInsetsGeometry? padding;
+
+ /// {@macro flutter.widgets.scroll_view.scrollDirection}
final Axis scrollDirection;
- /// Whether the scroll view scrolls in the reading direction.
- ///
- /// For example, if the reading direction is left-to-right and
- /// [scrollDirection] is [Axis.horizontal], then the scroll view scrolls from
- /// left to right when [reverse] is false and from right to left when
- /// [reverse] is true.
- ///
- /// Similarly, if [scrollDirection] is [Axis.vertical], then the scroll view
- /// scrolls from top to bottom when [reverse] is false and from bottom to top
- /// when [reverse] is true.
- ///
- /// Defaults to false.
+ /// {@macro flutter.widgets.scroll_view.reverse}
final bool reverse;
- /// An object that can be used to control the scroll view's scroll offset.
- ///
- /// Must be null if [primary] is true.
- ///
- /// A [ScrollController] serves several purposes. It can be used to control
- /// the initial scroll position (see [ScrollController.initialScrollOffset]).
- /// It can be used to control whether the scroll view should automatically
- /// save and restore its scroll position in the [PageStorage] (see
- /// [ScrollController.keepScrollOffset]). It can be used to read the current
- /// scroll position (see [ScrollController.offset]), or change it (see
- /// [ScrollController.animateTo]).
+ /// {@macro flutter.widgets.scroll_view.controller}
final ScrollController? controller;
- /// Whether this is the primary scroll view associated with the parent
- /// [PrimaryScrollController].
- ///
- /// On iOS, this identifies the scroll view that will scroll to top in
- /// response to a tap in the status bar.
- ///
- /// Defaults to true when [scrollDirection] is [Axis.vertical] and
- /// [controller] is null.
+ /// {@macro flutter.widgets.scroll_view.primary}
final bool? primary;
- /// How the scroll view should respond to user input.
- ///
- /// For example, determines how the scroll view continues to animate after the
- /// user stops dragging the scroll view.
- ///
- /// Defaults to matching platform conventions.
+ /// {@macro flutter.widgets.scroll_view.physics}
final ScrollPhysics? physics;
- /// Whether the extent of the scroll view in the [scrollDirection] should be
- /// determined by the contents being viewed.
- ///
- /// If the scroll view does not shrink wrap, then the scroll view will expand
- /// to the maximum allowed size in the [scrollDirection]. If the scroll view
- /// has unbounded constraints in the [scrollDirection], then [shrinkWrap] must
- /// be true.
- ///
- /// Shrink wrapping the content of the scroll view is significantly more
- /// expensive than expanding to the maximum allowed size because the content
- /// can expand and contract during scrolling, which means the size of the
- /// scroll view needs to be recomputed whenever the scroll position changes.
- ///
- /// Defaults to false.
+ /// {@macro flutter.widgets.scroll_view.shrinkWrap}
final bool shrinkWrap;
- /// The amount of space by which to inset the children.
- final EdgeInsetsGeometry? padding;
+ /// {@macro flutter.widgets.scroll_view.anchor}
+ final double anchor;
+
+ /// {@macro flutter.rendering.RenderViewportBase.cacheExtent}
+ final double? cacheExtent;
+
+ /// {@macro flutter.widgets.scrollable.dragStartBehavior}
+ final DragStartBehavior dragStartBehavior;
+
+ /// {@macro flutter.widgets.scroll_view.keyboardDismissBehavior}
+ ///
+ /// The default is [ScrollViewKeyboardDismissBehavior.manual]
+ final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;
+
+ /// {@macro flutter.widgets.scrollable.restorationId}
+ final String? restorationId;
+
+ /// {@macro flutter.material.Material.clipBehavior}
+ ///
+ /// Defaults to [Clip.hardEdge].
+ final Clip clipBehavior;
/// The state from the closest instance of this class that encloses the given
/// context.
@@ -334,6 +326,12 @@
primary: widget.primary,
physics: widget.physics,
shrinkWrap: widget.shrinkWrap,
+ anchor: widget.anchor,
+ cacheExtent: widget.cacheExtent,
+ dragStartBehavior: widget.dragStartBehavior,
+ keyboardDismissBehavior: widget.keyboardDismissBehavior,
+ restorationId: widget.restorationId,
+ clipBehavior: widget.clipBehavior,
slivers: <Widget>[
SliverPadding(
padding: widget.padding ?? EdgeInsets.zero,
@@ -386,28 +384,16 @@
}) : assert(itemCount >= 0),
super(key: key);
- /// Called, as needed, to build list item widgets.
- ///
- /// List items are only built when they're scrolled into view.
- ///
- /// The [IndexedWidgetBuilder] index parameter indicates the item's
- /// position in the list. The value of the index parameter will be between
- /// zero and one less than [itemCount]. All items in the list should have a
- /// unique [Key], and should have some kind of listener to start the drag
- /// (usually a [ReorderableDragStartListener] or
- /// [ReorderableDelayedDragStartListener]).
+ /// {@macro flutter.widgets.reorderable_list.itemBuilder}
final IndexedWidgetBuilder itemBuilder;
- /// The number of items in the list.
+ /// {@macro flutter.widgets.reorderable_list.itemCount}
final int itemCount;
- /// A callback used by the list to report that a list item has been dragged
- /// to a new location in the list and the application should update the order
- /// of the items.
+ /// {@macro flutter.widgets.reorderable_list.onReorder}
final ReorderCallback onReorder;
- /// A callback that allows the app to add an animated decoration around
- /// an item when it is being dragged.
+ /// {@macro flutter.widgets.reorderable_list.proxyDecorator}
final ReorderItemProxyDecorator? proxyDecorator;
@override
diff --git a/packages/flutter/lib/src/widgets/scroll_view.dart b/packages/flutter/lib/src/widgets/scroll_view.dart
index 29e27db..03558cb 100644
--- a/packages/flutter/lib/src/widgets/scroll_view.dart
+++ b/packages/flutter/lib/src/widgets/scroll_view.dart
@@ -109,11 +109,14 @@
physics = physics ?? (primary == true || (primary == null && controller == null && identical(scrollDirection, Axis.vertical)) ? const AlwaysScrollableScrollPhysics() : null),
super(key: key);
+ /// {@template flutter.widgets.scroll_view.scrollDirection}
/// The axis along which the scroll view scrolls.
///
/// Defaults to [Axis.vertical].
+ /// {@endtemplate}
final Axis scrollDirection;
+ /// {@template flutter.widgets.scroll_view.reverse}
/// Whether the scroll view scrolls in the reading direction.
///
/// For example, if the reading direction is left-to-right and
@@ -126,8 +129,10 @@
/// when [reverse] is true.
///
/// Defaults to false.
+ /// {@endtemplate}
final bool reverse;
+ /// {@template flutter.widgets.scroll_view.controller}
/// An object that can be used to control the position to which this scroll
/// view is scrolled.
///
@@ -140,8 +145,10 @@
/// [ScrollController.keepScrollOffset]). It can be used to read the current
/// scroll position (see [ScrollController.offset]), or change it (see
/// [ScrollController.animateTo]).
+ /// {@endtemplate}
final ScrollController? controller;
+ /// {@template flutter.widgets.scroll_view.primary}
/// Whether this is the primary scroll view associated with the parent
/// [PrimaryScrollController].
///
@@ -156,11 +163,13 @@
///
/// On iOS, this also identifies the scroll view that will scroll to top in
/// response to a tap in the status bar.
+ /// {@endtemplate}
///
/// Defaults to true when [scrollDirection] is [Axis.vertical] and
/// [controller] is null.
final bool primary;
+ /// {@template flutter.widgets.scroll_view.physics}
/// How the scroll view should respond to user input.
///
/// For example, determines how the scroll view continues to animate after the
@@ -195,8 +204,10 @@
/// dynamically, which can be relatively expensive, and it would be
/// inefficient to speculatively create this object each frame to see if the
/// physics should be updated.)
+ /// {@endtemplate}
final ScrollPhysics? physics;
+ /// {@template flutter.widgets.scroll_view.shrinkWrap}
/// Whether the extent of the scroll view in the [scrollDirection] should be
/// determined by the contents being viewed.
///
@@ -211,6 +222,7 @@
/// scroll view needs to be recomputed whenever the scroll position changes.
///
/// Defaults to false.
+ /// {@endtemplate}
final bool shrinkWrap;
/// The first child in the [GrowthDirection.forward] growth direction.
@@ -232,6 +244,7 @@
/// * [anchor], which controls where the [center] as aligned in the viewport.
final Key? center;
+ /// {@template flutter.widgets.scroll_view.anchor}
/// The relative position of the zero scroll offset.
///
/// For example, if [anchor] is 0.5 and the [AxisDirection] determined by
@@ -240,6 +253,7 @@
/// within the viewport. If the [anchor] is 1.0, and the axis direction is
/// [AxisDirection.right], then the zero scroll offset is on the left edge of
/// the viewport.
+ /// {@endtemplate}
final double anchor;
/// {@macro flutter.rendering.RenderViewportBase.cacheExtent}
@@ -263,8 +277,10 @@
/// {@macro flutter.widgets.scrollable.dragStartBehavior}
final DragStartBehavior dragStartBehavior;
+ /// {@template flutter.widgets.scroll_view.keyboardDismissBehavior}
/// [ScrollViewKeyboardDismissBehavior] the defines how this [ScrollView] will
/// dismiss the keyboard automatically.
+ /// {@endtemplate}
final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;
/// {@macro flutter.widgets.scrollable.restorationId}
diff --git a/packages/flutter/test/material/reorderable_list_test.dart b/packages/flutter/test/material/reorderable_list_test.dart
index 49bda2c..ee43af2 100644
--- a/packages/flutter/test/material/reorderable_list_test.dart
+++ b/packages/flutter/test/material/reorderable_list_test.dart
@@ -1153,6 +1153,34 @@
});
+
+ testWidgets('ReorderableListView.builder asserts on negative childCount', (WidgetTester tester) async {
+ expect(() => ReorderableListView.builder(
+ itemBuilder: (BuildContext context, int index) {
+ return const SizedBox();
+ },
+ itemCount: -1,
+ onReorder: (int from, int to) {},
+ ), throwsAssertionError);
+ });
+
+ testWidgets('ReorderableListView.builder only creates the children it needs', (WidgetTester tester) async {
+ final Set<int> itemsCreated = <int>{};
+ await tester.pumpWidget(MaterialApp(
+ home: ReorderableListView.builder(
+ itemBuilder: (BuildContext context, int index) {
+ itemsCreated.add(index);
+ return Text(index.toString(), key: ValueKey<int>(index));
+ },
+ itemCount: 1000,
+ onReorder: (int from, int to) {},
+ ),
+ ));
+
+ // Should have only created the first 18 items.
+ expect(itemsCreated, <int>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17});
+ });
+
testWidgets('ReorderableListView can be reversed', (WidgetTester tester) async {
final Widget reorderableListView = ReorderableListView(
children: const <Widget>[
diff --git a/packages/flutter_tools/lib/src/commands/attach.dart b/packages/flutter_tools/lib/src/commands/attach.dart
index ddc877b..4bf7a1a 100644
--- a/packages/flutter_tools/lib/src/commands/attach.dart
+++ b/packages/flutter_tools/lib/src/commands/attach.dart
@@ -5,6 +5,7 @@
import 'dart:async';
import 'package:meta/meta.dart';
+import 'package:vm_service/vm_service.dart';
import '../android/android_device.dart';
import '../artifacts.dart';
@@ -27,6 +28,7 @@
import '../run_cold.dart';
import '../run_hot.dart';
import '../runner/flutter_command.dart';
+import '../vmservice.dart';
/// A Flutter-command that attaches to applications that have been launched
/// without `flutter run`.
@@ -374,6 +376,11 @@
}
globals.printStatus('Waiting for a new connection from Flutter on ${device.name}...');
}
+ } on RPCError catch (err) {
+ if (err.code == RPCErrorCodes.kServiceDisappeared) {
+ throwToolExit('Lost connection to device.');
+ }
+ rethrow;
} finally {
final List<ForwardedPort> ports = device.portForwarder.forwardedPorts.toList();
for (final ForwardedPort port in ports) {
diff --git a/packages/flutter_tools/lib/src/commands/drive.dart b/packages/flutter_tools/lib/src/commands/drive.dart
index 8b8b343..86a1dd5 100644
--- a/packages/flutter_tools/lib/src/commands/drive.dart
+++ b/packages/flutter_tools/lib/src/commands/drive.dart
@@ -196,7 +196,7 @@
) ?? PackageConfig.empty;
final DriverService driverService = _flutterDriverFactory.createDriverService(web);
final BuildInfo buildInfo = await getBuildInfo();
- final DebuggingOptions debuggingOptions = await createDebuggingOptions();
+ final DebuggingOptions debuggingOptions = await createDebuggingOptions(web);
final File applicationBinary = stringArg('use-application-binary') == null
? null
: _fileSystem.file(stringArg('use-application-binary'));
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index e9cf68b..af9c4a9 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -4,6 +4,9 @@
import 'dart:async';
+import 'package:meta/meta.dart';
+import 'package:vm_service/vm_service.dart';
+
import '../android/android_device.dart';
import '../base/common.dart';
import '../base/file_system.dart';
@@ -20,6 +23,7 @@
import '../run_hot.dart';
import '../runner/flutter_command.dart';
import '../tracing.dart';
+import '../vmservice.dart';
import '../web/web_runner.dart';
import 'daemon.dart';
@@ -157,8 +161,8 @@
String get traceAllowlist => stringArg('trace-allowlist');
/// Create a debugging options instance for the current `run` or `drive` invocation.
- Future<DebuggingOptions> createDebuggingOptions() async {
- final BuildInfo buildInfo = await getBuildInfo();
+ Future<DebuggingOptions> createDebuggingOptions(bool webMode) async {
+ final BuildInfo buildInfo = await getBuildInfo(updateWebDefines: webMode);
final int browserDebugPort = featureFlags.isWebEnabled && argResults.wasParsed('web-browser-debug-port')
? int.parse(stringArg('web-browser-debug-port'))
: null;
@@ -320,6 +324,7 @@
final String description = 'Run your Flutter app on an attached device.';
List<Device> devices;
+ bool webMode = false;
String get userIdentifier => stringArg(FlutterOptions.kDeviceUser);
@@ -391,7 +396,7 @@
}
}
- final BuildInfo buildInfo = await getBuildInfo();
+ final BuildInfo buildInfo = await getBuildInfo(updateWebDefines: webMode);
final String modeName = buildInfo.modeName;
return <CustomDimensions, String>{
CustomDimensions.commandRunIsEmulator: '$isEmulator',
@@ -446,13 +451,64 @@
'--${FlutterOptions.kDeviceUser} is only supported for Android. At least one Android device is required.'
);
}
+ // Only support "web mode" with a single web device due to resident runner
+ // refactoring required otherwise.
+ webMode = featureFlags.isWebEnabled &&
+ devices.length == 1 &&
+ await devices.single.targetPlatform == TargetPlatform.web_javascript;
+ }
+
+ @visibleForTesting
+ Future<ResidentRunner> createRunner({
+ @required bool hotMode,
+ @required List<FlutterDevice> flutterDevices,
+ @required String applicationBinaryPath,
+ @required FlutterProject flutterProject,
+ }) async {
+ if (hotMode && !webMode) {
+ return HotRunner(
+ flutterDevices,
+ target: targetFile,
+ debuggingOptions: await createDebuggingOptions(webMode),
+ benchmarkMode: boolArg('benchmark'),
+ applicationBinary: applicationBinaryPath == null
+ ? null
+ : globals.fs.file(applicationBinaryPath),
+ projectRootPath: stringArg('project-root'),
+ dillOutputPath: stringArg('output-dill'),
+ stayResident: stayResident,
+ ipv6: ipv6,
+ );
+ } else if (webMode) {
+ return webRunnerFactory.createWebRunner(
+ flutterDevices.single,
+ target: targetFile,
+ flutterProject: flutterProject,
+ ipv6: ipv6,
+ debuggingOptions: await createDebuggingOptions(webMode),
+ stayResident: stayResident,
+ urlTunneller: null,
+ );
+ }
+ return ColdRunner(
+ flutterDevices,
+ target: targetFile,
+ debuggingOptions: await createDebuggingOptions(webMode),
+ traceStartup: traceStartup,
+ awaitFirstFrameWhenTracing: awaitFirstFrameWhenTracing,
+ applicationBinary: applicationBinaryPath == null
+ ? null
+ : globals.fs.file(applicationBinaryPath),
+ ipv6: ipv6,
+ stayResident: stayResident,
+ );
}
@override
Future<FlutterCommandResult> runCommand() async {
// Enable hot mode by default if `--no-hot` was not passed and we are in
// debug mode.
- final BuildInfo buildInfo = await getBuildInfo();
+ final BuildInfo buildInfo = await getBuildInfo(updateWebDefines: webMode);
final bool hotMode = shouldUseHotMode(buildInfo);
final String applicationBinaryPath = stringArg('use-application-binary');
@@ -474,7 +530,7 @@
try {
app = await daemon.appDomain.startApp(
devices.first, globals.fs.currentDirectory.path, targetFile, route,
- await createDebuggingOptions(), hotMode,
+ await createDebuggingOptions(webMode), hotMode,
applicationBinary: applicationBinaryPath == null
? null
: globals.fs.file(applicationBinaryPath),
@@ -548,51 +604,13 @@
platform: globals.platform,
),
];
- // Only support "web mode" with a single web device due to resident runner
- // refactoring required otherwise.
- final bool webMode = featureFlags.isWebEnabled &&
- devices.length == 1 &&
- await devices.single.targetPlatform == TargetPlatform.web_javascript;
- ResidentRunner runner;
- if (hotMode && !webMode) {
- runner = HotRunner(
- flutterDevices,
- target: targetFile,
- debuggingOptions: await createDebuggingOptions(),
- benchmarkMode: boolArg('benchmark'),
- applicationBinary: applicationBinaryPath == null
- ? null
- : globals.fs.file(applicationBinaryPath),
- projectRootPath: stringArg('project-root'),
- dillOutputPath: stringArg('output-dill'),
- stayResident: stayResident,
- ipv6: ipv6,
- );
- } else if (webMode) {
- runner = webRunnerFactory.createWebRunner(
- flutterDevices.single,
- target: targetFile,
- flutterProject: flutterProject,
- ipv6: ipv6,
- debuggingOptions: await createDebuggingOptions(),
- stayResident: stayResident,
- urlTunneller: null,
- );
- } else {
- runner = ColdRunner(
- flutterDevices,
- target: targetFile,
- debuggingOptions: await createDebuggingOptions(),
- traceStartup: traceStartup,
- awaitFirstFrameWhenTracing: awaitFirstFrameWhenTracing,
- applicationBinary: applicationBinaryPath == null
- ? null
- : globals.fs.file(applicationBinaryPath),
- ipv6: ipv6,
- stayResident: stayResident,
- );
- }
+ final ResidentRunner runner = await createRunner(
+ applicationBinaryPath: applicationBinaryPath,
+ flutterDevices: flutterDevices,
+ flutterProject: flutterProject,
+ hotMode: hotMode,
+ );
DateTime appStartedTime;
// Sync completer so the completing agent attaching to the resident doesn't
@@ -617,12 +635,19 @@
}
));
- final int result = await runner.run(
- appStartedCompleter: appStartedTimeRecorder,
- route: route,
- );
- if (result != 0) {
- throwToolExit(null, exitCode: result);
+ try {
+ final int result = await runner.run(
+ appStartedCompleter: appStartedTimeRecorder,
+ route: route,
+ );
+ if (result != 0) {
+ throwToolExit(null, exitCode: result);
+ }
+ } on RPCError catch (err) {
+ if (err.code == RPCErrorCodes.kServiceDisappeared) {
+ throwToolExit('Lost connection to device.');
+ }
+ rethrow;
}
return FlutterCommandResult(
ExitStatus.success,
diff --git a/packages/flutter_tools/lib/src/dart/language_version.dart b/packages/flutter_tools/lib/src/dart/language_version.dart
index b36faeb..9d60ab8 100644
--- a/packages/flutter_tools/lib/src/dart/language_version.dart
+++ b/packages/flutter_tools/lib/src/dart/language_version.dart
@@ -84,7 +84,7 @@
// If the language version cannot be found, use the package version.
if (package != null) {
- return package.languageVersion;
+ return package.languageVersion ?? nullSafeVersion;
}
// Default to 2.12
return nullSafeVersion;
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart
index 005ac07..b2d6c15 100644
--- a/packages/flutter_tools/lib/src/resident_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -1301,6 +1301,9 @@
}
await waitForExtension(device.vmService, 'ext.flutter.activeDevToolsServerAddress');
try {
+ if (_devToolsLauncher == null) {
+ return;
+ }
unawaited(invokeFlutterExtensionRpcRawOnFirstIsolate(
'ext.flutter.activeDevToolsServerAddress',
device: device,
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index 1d926b3..3f5e9bc 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -803,7 +803,7 @@
///
/// Throws a [ToolExit] if the current set of options is not compatible with
/// each other.
- Future<BuildInfo> getBuildInfo({ BuildMode forcedBuildMode }) async {
+ Future<BuildInfo> getBuildInfo({ BuildMode forcedBuildMode, bool updateWebDefines = true }) async {
final bool trackWidgetCreation = argParser.options.containsKey('track-widget-creation') &&
boolArg('track-widget-creation');
@@ -923,7 +923,7 @@
? stringsArg(FlutterOptions.kDartDefinesOption)
: <String>[];
- if (argParser.options.containsKey('web-renderer')) {
+ if (argParser.options.containsKey('web-renderer') && updateWebDefines) {
dartDefines = updateDartDefines(dartDefines, stringArg('web-renderer'));
}
diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart
index 9e45e55..5178606 100644
--- a/packages/flutter_tools/lib/src/vmservice.dart
+++ b/packages/flutter_tools/lib/src/vmservice.dart
@@ -777,7 +777,10 @@
kListViewsMethod,
);
if (response == null) {
- return null;
+ // The service may have disappeared mid-request.
+ // Return an empty list now, and let the shutdown logic elsewhere deal
+ // with cleaning up.
+ return <FlutterView>[];
}
final List<Object> rawViews = response.json['views'] as List<Object>;
final List<FlutterView> views = <FlutterView>[
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart
index 1b69f13..1cd71cb 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart
@@ -631,6 +631,82 @@
FileSystem: () => testFileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
+
+ testUsingContext('Catches service disappeared error', () async {
+ final MockAndroidDevice device = MockAndroidDevice();
+ final MockHotRunner mockHotRunner = MockHotRunner();
+ final MockHotRunnerFactory mockHotRunnerFactory = MockHotRunnerFactory();
+ when(device.portForwarder).thenReturn(const NoOpDevicePortForwarder());
+
+ when(mockHotRunner.attach(
+ appStartedCompleter: anyNamed('appStartedCompleter'),
+ allowExistingDdsInstance: true,
+ )).thenAnswer((_) async {
+ await null;
+ throw vm_service.RPCError('flutter._listViews', RPCErrorCodes.kServiceDisappeared, '');
+ });
+ when(mockHotRunnerFactory.build(
+ any,
+ target: anyNamed('target'),
+ debuggingOptions: anyNamed('debuggingOptions'),
+ packagesFilePath: anyNamed('packagesFilePath'),
+ flutterProject: anyNamed('flutterProject'),
+ ipv6: false,
+ )).thenReturn(mockHotRunner);
+
+ testDeviceManager.addDevice(device);
+ when(device.getLogReader(includePastLogs: anyNamed('includePastLogs')))
+ .thenAnswer((_) {
+ return NoOpDeviceLogReader('test');
+ });
+ testFileSystem.file('lib/main.dart').createSync();
+
+ final AttachCommand command = AttachCommand(hotRunnerFactory: mockHotRunnerFactory);
+ await expectLater(createTestCommandRunner(command).run(<String>[
+ 'attach',
+ ]), throwsToolExit(message: 'Lost connection to device.'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => testFileSystem,
+ ProcessManager: () => FakeProcessManager.any(),
+ });
+
+ testUsingContext('Does not catch generic RPC error', () async {
+ final MockAndroidDevice device = MockAndroidDevice();
+ final MockHotRunner mockHotRunner = MockHotRunner();
+ final MockHotRunnerFactory mockHotRunnerFactory = MockHotRunnerFactory();
+ when(device.portForwarder).thenReturn(const NoOpDevicePortForwarder());
+
+ when(mockHotRunner.attach(
+ appStartedCompleter: anyNamed('appStartedCompleter'),
+ allowExistingDdsInstance: true,
+ )).thenAnswer((_) async {
+ await null;
+ throw vm_service.RPCError('flutter._listViews', RPCErrorCodes.kInvalidParams, '');
+ });
+ when(mockHotRunnerFactory.build(
+ any,
+ target: anyNamed('target'),
+ debuggingOptions: anyNamed('debuggingOptions'),
+ packagesFilePath: anyNamed('packagesFilePath'),
+ flutterProject: anyNamed('flutterProject'),
+ ipv6: false,
+ )).thenReturn(mockHotRunner);
+
+ testDeviceManager.addDevice(device);
+ when(device.getLogReader(includePastLogs: anyNamed('includePastLogs')))
+ .thenAnswer((_) {
+ return NoOpDeviceLogReader('test');
+ });
+ testFileSystem.file('lib/main.dart').createSync();
+
+ final AttachCommand command = AttachCommand(hotRunnerFactory: mockHotRunnerFactory);
+ await expectLater(createTestCommandRunner(command).run(<String>[
+ 'attach',
+ ]), throwsA(isA<vm_service.RPCError>()));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => testFileSystem,
+ ProcessManager: () => FakeProcessManager.any(),
+ });
});
}
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart
index 8506b83..b3d1276 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart
@@ -17,11 +17,17 @@
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/run.dart';
+import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
+import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
+import 'package:flutter_tools/src/resident_runner.dart';
import 'package:flutter_tools/src/runner/flutter_command.dart';
+import 'package:flutter_tools/src/vmservice.dart';
+import 'package:meta/meta.dart';
import 'package:mockito/mockito.dart';
+import 'package:vm_service/vm_service.dart';
import '../../src/common.dart';
import '../../src/context.dart';
@@ -387,6 +393,66 @@
ProcessManager: () => mockProcessManager,
Usage: () => usage,
});
+
+ testUsingContext('No web renderer options are added to non web device', () async {
+ final FakeApplicationPackageFactory applicationPackageFactory = ApplicationPackageFactory.instance as FakeApplicationPackageFactory;
+ final RunCommand command = RunCommand();
+ final MockDevice mockDevice = MockDevice(TargetPlatform.ios);
+ when(mockDevice.supportsRuntimeMode(any)).thenAnswer((Invocation invocation) => true);
+ when(mockDevice.isLocalEmulator).thenAnswer((Invocation invocation) => Future<bool>.value(false));
+ when(mockDevice.getLogReader(app: anyNamed('app'))).thenReturn(FakeDeviceLogReader());
+ when(mockDevice.supportsFastStart).thenReturn(true);
+ when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) => Future<String>.value('iOS 13'));
+ applicationPackageFactory.package = PrebuiltIOSApp(projectBundleId: 'test');
+
+ DebuggingOptions debuggingOptions;
+
+ when(mockDevice.startApp(
+ any,
+ mainPath: anyNamed('mainPath'),
+ debuggingOptions: anyNamed('debuggingOptions'),
+ platformArgs: anyNamed('platformArgs'),
+ route: anyNamed('route'),
+ prebuiltApplication: anyNamed('prebuiltApplication'),
+ ipv6: anyNamed('ipv6'),
+ userIdentifier: anyNamed('userIdentifier'),
+ )).thenAnswer((Invocation invocation) {
+ debuggingOptions = invocation.namedArguments[#debuggingOptions] as DebuggingOptions;
+ return Future<LaunchResult>.value(LaunchResult.failed());
+ });
+
+ when(mockDeviceManager.getDevices()).thenAnswer(
+ (Invocation invocation) => Future<List<Device>>.value(<Device>[mockDevice])
+ );
+
+ when(mockDeviceManager.findTargetDevices(any, timeout: anyNamed('timeout'))).thenAnswer(
+ (Invocation invocation) => Future<List<Device>>.value(<Device>[mockDevice])
+ );
+
+ final Directory tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_run_test.');
+ tempDir.childDirectory('ios').childFile('AppDelegate.swift').createSync(recursive: true);
+ tempDir.childFile('.dart_tool/package_config')
+ ..createSync(recursive: true)
+ ..writeAsStringSync(json.encode(<String, Object>{'configVersion': 2, 'packages': <Object>[]}));
+ tempDir.childDirectory('lib').childFile('main.dart').createSync(recursive: true);
+ tempDir.childFile('pubspec.yaml').writeAsStringSync('name: test');
+ globals.fs.currentDirectory = tempDir;
+
+ await expectToolExitLater(createTestCommandRunner(command).run(<String>[
+ 'run',
+ '--no-pub',
+ '--no-hot',
+ ]), isNull);
+ // No web renderer options are added.
+ expect(debuggingOptions.buildInfo.dartDefines, isEmpty);
+ }, overrides: <Type, Generator>{
+ Artifacts: () => artifacts,
+ Cache: () => mockCache,
+ DeviceManager: () => mockDeviceManager,
+ FileSystem: () => fs,
+ ProcessManager: () => mockProcessManager,
+ ApplicationPackageFactory: () => FakeApplicationPackageFactory(),
+ });
});
testUsingContext('should only request artifacts corresponding to connected devices', () async {
@@ -480,6 +546,30 @@
expect(dartDefines, <String>['FLUTTER_WEB_AUTO_DETECT=false','FLUTTER_WEB_USE_SKIA=false']);
});
});
+
+ testUsingContext('Flutter run catches service has disappear errors and throws a tool exit', () async {
+ final FakeResidentRunner residentRunner = FakeResidentRunner();
+ residentRunner.rpcError = RPCError('flutter._listViews', RPCErrorCodes.kServiceDisappeared, '');
+ final TestRunCommandWithFakeResidentRunner command = TestRunCommandWithFakeResidentRunner();
+ command.fakeResidentRunner = residentRunner;
+
+ await expectToolExitLater(createTestCommandRunner(command).run(<String>[
+ 'run',
+ '--no-pub',
+ ]), contains('Lost connection to device.'));
+ });
+
+ testUsingContext('Flutter run does not catch other RPC errors', () async {
+ final FakeResidentRunner residentRunner = FakeResidentRunner();
+ residentRunner.rpcError = RPCError('flutter._listViews', RPCErrorCodes.kInvalidParams, '');
+ final TestRunCommandWithFakeResidentRunner command = TestRunCommandWithFakeResidentRunner();
+ command.fakeResidentRunner = residentRunner;
+
+ await expectLater(() => createTestCommandRunner(command).run(<String>[
+ 'run',
+ '--no-pub',
+ ]), throwsA(isA<RPCError>()));
+ });
}
class MockCache extends Mock implements Cache {}
@@ -524,7 +614,7 @@
bool supportsRuntimeMode(BuildMode mode) => true;
@override
- bool get supportsHotReload => false;
+ bool supportsHotReload = false;
@override
bool get supportsFastStart => false;
@@ -579,3 +669,54 @@
return null;
}
}
+
+class FakeApplicationPackageFactory extends Fake implements ApplicationPackageFactory {
+ ApplicationPackage package;
+
+ @override
+ Future<ApplicationPackage> getPackageForPlatform(
+ TargetPlatform platform, {
+ BuildInfo buildInfo,
+ File applicationBinary,
+ }) async {
+ return package;
+ }
+}
+
+class TestRunCommandWithFakeResidentRunner extends RunCommand {
+ FakeResidentRunner fakeResidentRunner;
+
+ @override
+ Future<ResidentRunner> createRunner({
+ @required bool hotMode,
+ @required List<FlutterDevice> flutterDevices,
+ @required String applicationBinaryPath,
+ @required FlutterProject flutterProject,
+ }) async {
+ return fakeResidentRunner;
+ }
+
+ @override
+ // ignore: must_call_super
+ Future<void> validateCommand() async {
+ devices = <Device>[FakeDevice()..supportsHotReload = true];
+ }
+}
+
+class FakeResidentRunner extends Fake implements ResidentRunner {
+ RPCError rpcError;
+
+ @override
+ Future<int> run({
+ Completer<DebugConnectionInfo> connectionInfoCompleter,
+ Completer<void> appStartedCompleter,
+ bool enableDevTools = false,
+ String route,
+ }) async {
+ await null;
+ if (rpcError != null) {
+ throw rpcError;
+ }
+ return 0;
+ }
+}
diff --git a/packages/flutter_tools/test/general.shard/dart/language_version_test.dart b/packages/flutter_tools/test/general.shard/dart/language_version_test.dart
index 1abebfc..47bb5a0 100644
--- a/packages/flutter_tools/test/general.shard/dart/language_version_test.dart
+++ b/packages/flutter_tools/test/general.shard/dart/language_version_test.dart
@@ -252,6 +252,20 @@
expect(determineLanguageVersion(file, package), LanguageVersion(2, 7));
});
+ testWithoutContext('defaults to null safe version if package lookup returns null', () {
+ final FileSystem fileSystem = MemoryFileSystem.test();
+ final File file = fileSystem.file('example.dart')
+ ..writeAsStringSync('''
+// Some license
+''');
+ final Package package = Package(
+ 'foo',
+ Uri.parse('file://foo/'),
+ languageVersion: null,
+ );
+
+ expect(determineLanguageVersion(file, package), LanguageVersion(2, 12));
+ });
testWithoutContext('Returns null safe error if reading the file throws a FileSystemException', () {
final Package package = Package(
diff --git a/packages/flutter_tools/test/general.shard/vmservice_test.dart b/packages/flutter_tools/test/general.shard/vmservice_test.dart
index 2edce09..57b1547 100644
--- a/packages/flutter_tools/test/general.shard/vmservice_test.dart
+++ b/packages/flutter_tools/test/general.shard/vmservice_test.dart
@@ -354,7 +354,7 @@
expect(skSLs, isNull);
final List<FlutterView> views = await fakeVmServiceHost.vmService.getFlutterViews();
- expect(views, isNull);
+ expect(views, isEmpty);
final vm_service.Response screenshot = await fakeVmServiceHost.vmService.screenshot();
expect(screenshot, isNull);