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/59413
This relocates `mock_canvas.dart` and `recording_canvas.dart` from `flutter/test/rendering` to `flutter_test`.
The testing functionality afforded by mock_canvas should be available to everyone, not just the framework. :)
mock_canvas.dart needed a bit of cleanup - things like formatting and super parameters.
This adds a macrobenchmark representative of a real world application that uses SVG icons. The scenario of rasterizing complex paths that don't change over time does not seem to be covered by any other macrobenchmark and shows a significantly slower impeller performance compared to skia.
It's actually bit problematic to measure this because on A15 the CPU load with impeller is high enough to trigger CPU frequency change. So in order to get consistent reading I had to add a spinning background thread that would keep the CPU at highest frequency.
```objc
[NSThread detachNewThreadWithBlock:^{
while (true) {
pthread_yield_np();
}
}];
```
```bash
flutter drive --profile --local-engine=ios_profile -t test_driver/run_app.dart --driver test_driver/path_tessellation_static_perf_test.dart
```
| average_frame_build_time_millis |Time|
|--|--|
| Impeller | 0.46686524822695047 |
| Skia | 0.4625749999999999 |
| Skia - No RasterCache | 0.47173750000000086|
| average_frame_rasterizer_time_millis | Time |
|--|--|
| Impeller | 6.654328519855595 |
| Skia - Raster Cache | 0.2534123711340209 * |
| Skia - No RasterCache | 0.53424375 |
* Adding the `GeometryPainter` seems to have triggered the complexity threshold for raster cache.
<img alt="screenshot" width="320" src="https://github.com/flutter/flutter/assets/96958/7a2f9384-b512-477b-bffa-058d4d284a41"/>
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].*
In order to avoid new tasks from not being scheduled we are not going to backfill the autoroller because it can sometimes play transactions out of order causing issues with tasks that are scheduled but not known.
*List which issues are fixed by this PR. You must list at least one issue.*
Part of https://github.com/flutter/flutter/issues/127063
*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 reduce the execution time of macrobenchmarks driver tests.
I tried to find the exact size to scroll the screen but I couldn't find a way to do that with driver tests.
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.
Redo of https://github.com/flutter/flutter/pull/130728 - code is the same as before. That PR was stuck in Google testing and then I messed up the rebase so started over.
----
Starting in Xcode 15, the simulator is no longer included in Xcode and must be downloaded and installed separately. This adds a validation to `flutter doctor` to warn when the needed simulator runtime is missing.
Validation message looks like:
```
[!] Xcode - develop for iOS and macOS (Xcode 15.0)
! iOS 17.0 Simulator not installed; this may be necessary for iOS and macOS development.
To download and install the platform, open Xcode, select Xcode > Settings > Platforms,
and click the GET button for the required platform.
For more information, please visit:
https://developer.apple.com/documentation/xcode/installing-additional-simulator-runtimes
```
It may also show an error like this when something goes wrong when checking for the simulator:
```
[!] Xcode - develop for iOS and macOS (Xcode 15.0)
â Unable to find the iPhone Simulator SDK.
```
Note: I'm unsure of in the future if the SDK and the simulator runtime will need to match the exact version or just the major. For now, it only checks against the major version.
Part 3 of https://github.com/flutter/flutter/issues/129558.