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 _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
|
||||
/// necessary.
|
||||
///
|
||||
@ -855,7 +866,7 @@ mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererB
|
||||
/// ensure the widget, element, and render trees are all built.
|
||||
void runApp(Widget app) {
|
||||
WidgetsFlutterBinding.ensureInitialized()
|
||||
..attachRootWidget(app)
|
||||
..scheduleAttachRootWidget(app)
|
||||
..scheduleWarmUpFrame();
|
||||
}
|
||||
|
||||
|
@ -498,13 +498,8 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont
|
||||
|
||||
bool get hasScrolledBody {
|
||||
for (_NestedScrollPosition position in _innerPositions) {
|
||||
// TODO(chunhtai): Replace null check with assert once
|
||||
// https://github.com/flutter/flutter/issues/31195 is fixed.
|
||||
if (
|
||||
position.minScrollExtent != null &&
|
||||
position.pixels != null &&
|
||||
position.pixels > position.minScrollExtent
|
||||
) {
|
||||
assert(position.minScrollExtent != null && position.pixels != null);
|
||||
if (position.pixels > position.minScrollExtent) {
|
||||
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();
|
||||
}
|
||||
|
||||
@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
|
||||
Future<void> idle() {
|
||||
final Future<void> result = super.idle();
|
||||
|
Loading…
x
Reference in New Issue
Block a user