## Description
Fix three memory leaks detected by `about_test.dart`, but were really in the `Route` and `OverlayEntry` classes.
## Related Issues
- Fixes https://github.com/flutter/flutter/issues/130354
## Tests
- Updates about_test.dart to not ignore the leaks anymore.
- slightly improved assert message when row cell counts don't match column count.
- more breadcrumbs in API documentation. more documentation in general.
- added more documentation for the direction of the "ascending" arrow.
- two samples for PaginatedDataTable.
- make PaginatedDataTable support hot reloading across changes to the number of columns.
- introduce matrix3MoreOrLessEquals. An earlier version of this PR used it in tests, but eventually it was not needed. The function seems useful to keep though.
On native iOS and Android when long pressing and then dragging the selection expands word by word. Before this change `SelectionArea` expanded the selection character by character on a long press drag.
Fixes#104603
Reverts flutter/flutter#131609
b/295497265 - This broke Google Testing. We'll need the internal patch
before landing as there's a large number of customer codebases impacted.
Move `SKPARAGRAPH_REMOVE_ROUNDING_HACK` reading to the framework from `dart:ui` so `flutter run --dart-define="SKPARAGRAPH_REMOVE_ROUNDING_HACK=false"` works
This change makes sure the style provided through the `TextField`s `style` parameter is resolved for material states before merging it with defaults.
Fixes#132212
Adds a getter to access the value of the private `RestorableBool _hasInteractedByUser`.
*List which issues are fixed by this PR. You must list at least one issue.*
Fixes#131538
Fixes https://github.com/flutter/flutter/issues/126297
This adds support for keep alive to the 2D scrolling foundation. The TwoDimensionalChildBuilderDelegate and TwoDimensionalChildListDelegate will both add automatic keep alives to their children, matching the convention from SliverChildDelegates. The TwoDimensionalViewportParentData now incorporates keep alive and which is managed by the RenderTwoDimensionalViewport.
Use a null-aware `?.` operator instead of a conditional operator. This complies with the `prefer_null_aware_operators` rule. Fixes https://github.com/flutter/flutter/issues/132241
*If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].*
Migrate tests in flutter/flutter. Once the tests here and in `*_customer_testing` are migrated, the default value of the migration flag will be changed from false to true, making the rounding hack disabled by default.
I was debugging an Overlay issue and felt I could have identified the problem faster if the existing assertions provided more information about the current state of the OverlayEntry and Overlay.
This wraps up the platform view improvements discussed in https://github.com/flutter/flutter/issues/127030.
- Splits `HtmlElementView` into 2 files that are conditionally imported.
- The non-web version can be instantiated but it throws if it ends up being built in a widget tree.
- Out-of-the-box view factories that create visible & invisible DOM elements given a `tagName` parameter.
- New `HtmlElementView.fromTagName()` constructor that uses the default factories to create DOM elements.
- Tests covering the new API.
Depends on https://github.com/flutter/engine/pull/43828
Fixes https://github.com/flutter/flutter/issues/127030
Fixes https://github.com/flutter/flutter/issues/89939 and updates the look of the Android context menu to match API 34.
## The problem
Before this PR, setting `surface` in the color scheme caused the background color of the Android context menu to change, but it wasn't possible to change the text color.
```dart
MaterialApp(
theme: ThemeData(
// Using a dark theme made the context menu text color be white.
colorScheme: ThemeData.dark().colorScheme.copyWith(
// Setting the surface here worked.
surface: Colors.white,
// But there was no way to set the text color. This didn't work.
onSurface: Colors.black,
),
),
),
```
| Expected (after PR) | Actual (before PR) |
| --- | --- |
| <img width="239" alt="Screenshot 2023-08-07 at 11 45 37 AM" src="https://github.com/flutter/flutter/assets/389558/a9fb75e5-b6c3-4f8e-8c59-2021780c44a7"> | <img width="250" alt="Screenshot 2023-08-07 at 11 51 10 AM" src="https://github.com/flutter/flutter/assets/389558/a5abd2d2-49bb-47a0-836f-864d56af2f58"> |
## Other examples
<table>
<tr>
<th>Scenario</th>
<th>Result</th>
</tr>
<tr>
<td>
```dart
MaterialApp(
theme: ThemeData(
colorScheme: ThemeData.light(),
),
...
),
```
</td>
<td>
<img width="244" alt="Screenshot 2023-08-07 at 11 42 05 AM" src="https://github.com/flutter/flutter/assets/389558/74c6870b-5ff7-4b1a-9e0c-b2bb4809ef1e">
</td>
</tr>
<tr>
<td>
```dart
MaterialApp(
theme: ThemeData(
colorScheme: ThemeData.dark(),
),
...
),
```
</td>
<td>
<img width="239" alt="Screenshot 2023-08-07 at 11 42 23 AM" src="https://github.com/flutter/flutter/assets/389558/91fe32f8-bd62-4d9b-96e8-ae5a9a769745">
</td>
</tr>
<tr>
<td>
```dart
MaterialApp(
theme: ThemeData(
colorScheme: ThemeData.light().colorScheme.copyWith(
surface: Colors.blue,
onSurface: Colors.red,
),
),
...
),
```
</td>
<td>
<img width="240" alt="Screenshot 2023-08-07 at 11 43 06 AM" src="https://github.com/flutter/flutter/assets/389558/e5752f8b-3738-4391-9055-15c38bd4af21">
</td>
</tr>
<tr>
<td>
```dart
MaterialApp(
theme: ThemeData(
colorScheme: ThemeData.light().colorScheme.copyWith(
surface: Colors.blue,
onSurface: Colors.red,
),
),
...
),
```
</td>
<td>
<img width="244" alt="Screenshot 2023-08-07 at 11 42 47 AM" src="https://github.com/flutter/flutter/assets/389558/68cc68f0-b338-4d94-8810-d8e46fb1e48e">
</td>
</tr>
</table>
This is a follow up to the following pull requests:
- https://github.com/flutter/flutter/pull/124514
I was finally able to reproduce this bug and found out why it was happening. Consider this code:
```dart
GestureDetector(
behavior: HitTestBehavior.translucent,
// Note: Make sure onTap is not null to ensure events
// are captured by `GestureDetector`
onTap: () {},
child: _shouldShowSlider
? Slider(value: _value, onChanged: _handleSlide)
: const SizedBox.shrink().
)
```
Runtime exception happens when:
1. User taps and holds the Slider (drag callback captured by `GestureDetector`)
2. `_shouldShowSlider` changes to false, Slider disappears and unmounts, and unregisters `_handleSlide`. But the callback is still registered by `GestureDetector`
3. Users moves finger as if Slider were still there
4. Drag callback is invoked, `_SliderState.showValueIndicator` is called
5. Exception - Slider is already disposed
This pull request fixes it by adding a mounted check inside `_SliderState.showValueIndicator` to ensure the Slider is actually mounted at the time of invoking drag event callback. I've added a unit test that will fail without this change.
The error stack trace is:
```
The following assertion was thrown while handling a gesture:
This widget has been unmounted, so the State no longer has a context (and should be considered
defunct).
Consider canceling any active work during "dispose" or using the "mounted" getter to determine if
the State is still active.
When the exception was thrown, this was the stack:
#0 State.context.<anonymous closure> (package:flutter/src/widgets/framework.dart:950:9)
#1 State.context (package:flutter/src/widgets/framework.dart:956:6)
#2 _SliderState.showValueIndicator (package:flutter/src/material/slider.dart:968:18)
#3 _RenderSlider._startInteraction (package:flutter/src/material/slider.dart:1487:12)
#4 _RenderSlider._handleDragStart (package:flutter/src/material/slider.dart:1541:5)
#5 DragGestureRecognizer._checkStart.<anonymous closure> (package:flutter/src/gestures/monodrag.dart:531:53)
#6 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:275:24)
#7 DragGestureRecognizer._checkStart (package:flutter/src/gestures/monodrag.dart:531:7)
#8 DragGestureRecognizer._checkDrag (package:flutter/src/gestures/monodrag.dart:498:5)
#9 DragGestureRecognizer.acceptGesture (package:flutter/src/gestures/monodrag.dart:431:7)
#10 _CombiningGestureArenaMember.acceptGesture (package:flutter/src/gestures/team.dart:45:14)
#11 GestureArenaManager._resolveInFavorOf (package:flutter/src/gestures/arena.dart:281:12)
#12 GestureArenaManager._resolve (package:flutter/src/gestures/arena.dart:239:9)
#13 GestureArenaEntry.resolve (package:flutter/src/gestures/arena.dart:53:12)
#14 _CombiningGestureArenaMember._resolve (package:flutter/src/gestures/team.dart:85:15)
#15 _CombiningGestureArenaEntry.resolve (package:flutter/src/gestures/team.dart:19:15)
#16 OneSequenceGestureRecognizer.resolve (package:flutter/src/gestures/recognizer.dart:375:13)
#17 DragGestureRecognizer.handleEvent (package:flutter/src/gestures/monodrag.dart:414:13)
#18 PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:98:12)
#19 PointerRouter._dispatchEventToRoutes.<anonymous closure> (package:flutter/src/gestures/pointer_router.dart:143:9)
#20 _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:625:13)
#21 PointerRouter._dispatchEventToRoutes (package:flutter/src/gestures/pointer_router.dart:141:18)
#22 PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:127:7)
#23 GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:488:19)
#24 GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:468:22)
#25 RendererBinding.dispatchEvent (package:flutter/src/rendering/binding.dart:439:11)
#26 GestureBinding._handlePointerEventImmediately (package:flutter/src/gestures/binding.dart:413:7)
#27 GestureBinding.handlePointerEvent (package:flutter/src/gestures/binding.dart:376:5)
#28 GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:323:7)
#29 GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:292:9)
#30 _invoke1 (dart:ui/hooks.dart:186:13)
#31 PlatformDispatcher._dispatchPointerDataPacket (dart:ui/platform_dispatcher.dart:433:7)
#32 _dispatchPointerDataPacket (dart:ui/hooks.dart:119:31)
Handler: "onStart"
Recognizer:
HorizontalDragGestureRecognizer#a5df2
```
*List which issues are fixed by this PR. You must list at least one issue.*
Internal bug: b/273666179, b/192329942
*If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].*
This PR aims to support Android's predictive back gesture when popping the entire Flutter app. Predictive route transitions between routes inside of a Flutter app will come later.
<img width="200" src="https://user-images.githubusercontent.com/389558/217918109-945febaa-9086-41cc-a476-1a189c7831d8.gif" />
### Trying it out
If you want to try this feature yourself, here are the necessary steps:
1. Run Android 33 or above.
1. Enable the feature flag for predictive back on the device under "Developer
options".
1. Create a Flutter project, or clone [my example project](https://github.com/justinmc/flutter_predictive_back_examples).
1. Set `android:enableOnBackInvokedCallback="true"` in
android/app/src/main/AndroidManifest.xml (already done in the example project).
1. Check out this branch.
1. Run the app. Perform a back gesture (swipe from the left side of the
screen).
You should see the predictive back animation like in the animation above and be able to commit or cancel it.
### go_router support
go_router works with predictive back out of the box because it uses a Navigator internally that dispatches NavigationNotifications!
~~go_router can be supported by adding a listener to the router and updating SystemNavigator.setFrameworkHandlesBack.~~
Similar to with nested Navigators, nested go_routers is supported by using a PopScope widget.
<details>
<summary>Full example of nested go_routers</summary>
```dart
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:go_router/go_router.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
void main() => runApp(_MyApp());
class _MyApp extends StatelessWidget {
final GoRouter router = GoRouter(
routes: <RouteBase>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) => _HomePage(),
),
GoRoute(
path: '/nested_navigators',
builder: (BuildContext context, GoRouterState state) => _NestedGoRoutersPage(),
),
],
);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: router,
);
}
}
class _HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Nested Navigators Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('Home Page'),
const Text('A system back gesture here will exit the app.'),
const SizedBox(height: 20.0),
ListTile(
title: const Text('Nested go_router route'),
subtitle: const Text('This route has another go_router in addition to the one used with MaterialApp above.'),
onTap: () {
context.push('/nested_navigators');
},
),
],
),
),
);
}
}
class _NestedGoRoutersPage extends StatefulWidget {
@override
State<_NestedGoRoutersPage> createState() => _NestedGoRoutersPageState();
}
class _NestedGoRoutersPageState extends State<_NestedGoRoutersPage> {
late final GoRouter _router;
final GlobalKey<NavigatorState> _nestedNavigatorKey = GlobalKey<NavigatorState>();
// If the nested navigator has routes that can be popped, then we want to
// block the root navigator from handling the pop so that the nested navigator
// can handle it instead.
bool get _popEnabled {
// canPop will throw an error if called before build. Is this the best way
// to avoid that?
return _nestedNavigatorKey.currentState == null ? true : !_router.canPop();
}
void _onRouterChanged() {
// Here the _router reports the location correctly, but canPop is still out
// of date. Hence the post frame callback.
SchedulerBinding.instance.addPostFrameCallback((Duration duration) {
setState(() {});
});
}
@override
void initState() {
super.initState();
final BuildContext rootContext = context;
_router = GoRouter(
navigatorKey: _nestedNavigatorKey,
routes: [
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) => _LinksPage(
title: 'Nested once - home route',
backgroundColor: Colors.indigo,
onBack: () {
rootContext.pop();
},
buttons: <Widget>[
TextButton(
onPressed: () {
context.push('/two');
},
child: const Text('Go to another route in this nested Navigator'),
),
],
),
),
GoRoute(
path: '/two',
builder: (BuildContext context, GoRouterState state) => _LinksPage(
backgroundColor: Colors.indigo.withBlue(255),
title: 'Nested once - page two',
),
),
],
);
_router.addListener(_onRouterChanged);
}
@override
void dispose() {
_router.removeListener(_onRouterChanged);
super.dispose();
}
@override
Widget build(BuildContext context) {
return PopScope(
popEnabled: _popEnabled,
onPopped: (bool success) {
if (success) {
return;
}
_router.pop();
},
child: Router<Object>.withConfig(
restorationScopeId: 'router-2',
config: _router,
),
);
}
}
class _LinksPage extends StatelessWidget {
const _LinksPage ({
required this.backgroundColor,
this.buttons = const <Widget>[],
this.onBack,
required this.title,
});
final Color backgroundColor;
final List<Widget> buttons;
final VoidCallback? onBack;
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: backgroundColor,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(title),
//const Text('A system back here will go back to Nested Navigators Page One'),
...buttons,
TextButton(
onPressed: onBack ?? () {
context.pop();
},
child: const Text('Go back'),
),
],
),
),
);
}
}
```
</details>
### Resources
Fixes https://github.com/flutter/flutter/issues/109513
Depends on engine PR https://github.com/flutter/engine/pull/39208✔️
Design doc: https://docs.google.com/document/d/1BGCWy1_LRrXEB6qeqTAKlk-U2CZlKJ5xI97g45U7azk/edit#
Migration guide: https://github.com/flutter/website/pull/8952
fixes https://github.com/flutter/flutter/issues/131931
Every time I search for the time picker in the API docs I end up in the `TimePickerDialog` docs.
We should link `showTimePicker` so it can be easier to reach it and mention it can be used directly to show a dialog with a time picker.
When providing infinite values for the control points of CatmullRomSpline, a StackOverflowError occurs. This asserts against that and provides a helpful error message.
Fixes https://github.com/flutter/flutter/issues/131246
Fixes https://github.com/flutter/flutter/issues/131627
Originally this code sometimes was returning null and sometimes was failing, when stack frame is in unexpected format.
This PR updates for one of the code paths from failing to returning null.