fix widget built twice during warm up frame (#39079)
This commit is contained in:
parent
296e97f322
commit
57d714ebb8
@ -794,6 +794,17 @@ mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererB
|
|||||||
Element get renderViewElement => _renderViewElement;
|
Element get renderViewElement => _renderViewElement;
|
||||||
Element _renderViewElement;
|
Element _renderViewElement;
|
||||||
|
|
||||||
|
/// Schedules a [Timer] for attaching the root widget.
|
||||||
|
///
|
||||||
|
/// This is called by [runApp] to configure the widget tree. Consider using
|
||||||
|
/// [attachRootWidget] if you want to build the widget tree synchronously.
|
||||||
|
@protected
|
||||||
|
void scheduleAttachRootWidget(Widget rootWidget) {
|
||||||
|
Timer.run(() {
|
||||||
|
attachRootWidget(rootWidget);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Takes a widget and attaches it to the [renderViewElement], creating it if
|
/// Takes a widget and attaches it to the [renderViewElement], creating it if
|
||||||
/// necessary.
|
/// necessary.
|
||||||
///
|
///
|
||||||
@ -855,7 +866,7 @@ mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererB
|
|||||||
/// ensure the widget, element, and render trees are all built.
|
/// ensure the widget, element, and render trees are all built.
|
||||||
void runApp(Widget app) {
|
void runApp(Widget app) {
|
||||||
WidgetsFlutterBinding.ensureInitialized()
|
WidgetsFlutterBinding.ensureInitialized()
|
||||||
..attachRootWidget(app)
|
..scheduleAttachRootWidget(app)
|
||||||
..scheduleWarmUpFrame();
|
..scheduleWarmUpFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -498,13 +498,8 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont
|
|||||||
|
|
||||||
bool get hasScrolledBody {
|
bool get hasScrolledBody {
|
||||||
for (_NestedScrollPosition position in _innerPositions) {
|
for (_NestedScrollPosition position in _innerPositions) {
|
||||||
// TODO(chunhtai): Replace null check with assert once
|
assert(position.minScrollExtent != null && position.pixels != null);
|
||||||
// https://github.com/flutter/flutter/issues/31195 is fixed.
|
if (position.pixels > position.minScrollExtent) {
|
||||||
if (
|
|
||||||
position.minScrollExtent != null &&
|
|
||||||
position.pixels != null &&
|
|
||||||
position.pixels > position.minScrollExtent
|
|
||||||
) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
// Copyright 2019 The Chromium 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:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:flutter/gestures.dart' show DragStartBehavior;
|
|
||||||
import 'package:quiver/testing/async.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
setUp(() {
|
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
|
||||||
WidgetsBinding.instance.resetEpoch();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('NestedScrollView can build sccessfully if mark dirty during warm up frame', () {
|
|
||||||
final FakeAsync fakeAsync = FakeAsync();
|
|
||||||
fakeAsync.run((FakeAsync async) {
|
|
||||||
runApp(
|
|
||||||
MaterialApp(
|
|
||||||
home: Material(
|
|
||||||
child: DefaultTabController(
|
|
||||||
length: 1,
|
|
||||||
child: NestedScrollView(
|
|
||||||
dragStartBehavior: DragStartBehavior.down,
|
|
||||||
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
|
||||||
return <Widget>[
|
|
||||||
const SliverPersistentHeader(
|
|
||||||
delegate: TestHeader(),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
},
|
|
||||||
body: SingleChildScrollView(
|
|
||||||
dragStartBehavior: DragStartBehavior.down,
|
|
||||||
child: Container(
|
|
||||||
height: 1000.0,
|
|
||||||
child: const Placeholder(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
// Marks element as dirty right before the first draw frame is called.
|
|
||||||
// This can happen when engine flush user setting.
|
|
||||||
final Element element = find.byType(NestedScrollView, skipOffstage: false).evaluate().single;
|
|
||||||
element.markNeedsBuild();
|
|
||||||
// Triggers draw frame timer scheduled in scheduleWarmUpFrame.
|
|
||||||
fakeAsync.flushTimers();
|
|
||||||
});
|
|
||||||
// Make sure widget is rebuilt correctly.
|
|
||||||
expect(
|
|
||||||
find.byType(NestedScrollView, skipOffstage: false).evaluate().single.widget is NestedScrollView,
|
|
||||||
isTrue
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class TestHeader extends SliverPersistentHeaderDelegate {
|
|
||||||
const TestHeader();
|
|
||||||
@override
|
|
||||||
double get minExtent => 100.0;
|
|
||||||
@override
|
|
||||||
double get maxExtent => 100.0;
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
|
|
||||||
return const Placeholder();
|
|
||||||
}
|
|
||||||
@override
|
|
||||||
bool shouldRebuild(TestHeader oldDelegate) => false;
|
|
||||||
}
|
|
31
packages/flutter/test/widgets/run_app_async_test.dart
Normal file
31
packages/flutter/test/widgets/run_app_async_test.dart
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// Copyright 2016 The Chromium 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:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:quiver/testing/async.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
setUp(() {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
WidgetsBinding.instance.resetEpoch();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('WidgetBinding build rendering tree and warm up frame back to back', () {
|
||||||
|
final FakeAsync fakeAsync = FakeAsync();
|
||||||
|
fakeAsync.run((FakeAsync async) {
|
||||||
|
runApp(
|
||||||
|
const MaterialApp(
|
||||||
|
home: Material(
|
||||||
|
child: Text('test'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
// Rendering tree is not built synchronously.
|
||||||
|
expect(WidgetsBinding.instance.renderViewElement, isNull);
|
||||||
|
fakeAsync.flushTimers();
|
||||||
|
expect(WidgetsBinding.instance.renderViewElement, isNotNull);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -993,6 +993,14 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
|
|||||||
_currentFakeAsync.flushMicrotasks();
|
_currentFakeAsync.flushMicrotasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void scheduleAttachRootWidget(Widget rootWidget) {
|
||||||
|
// We override the default version of this so that the application-startup widget tree
|
||||||
|
// build does not schedule timers which we might never get around to running.
|
||||||
|
attachRootWidget(rootWidget);
|
||||||
|
_currentFakeAsync.flushMicrotasks();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> idle() {
|
Future<void> idle() {
|
||||||
final Future<void> result = super.idle();
|
final Future<void> result = super.idle();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user