binding.dart 19.7 KB
Newer Older
1 2 3 4
// Copyright 2015 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.

5
import 'dart:developer' as developer;
6 7
import 'dart:ui' as ui show window;
import 'dart:ui' show AppLifecycleState, Locale;
8

Ian Hickson's avatar
Ian Hickson committed
9
import 'package:flutter/gestures.dart';
10
import 'package:flutter/rendering.dart';
11
import 'package:flutter/scheduler.dart';
Ian Hickson's avatar
Ian Hickson committed
12
import 'package:flutter/services.dart';
13
import 'package:meta/meta.dart';
14

15
import 'app.dart';
16
import 'framework.dart';
17

18 19
export 'dart:ui' show AppLifecycleState, Locale;

20 21
/// Interface for classes that register with the Widgets layer binding.
///
22
/// See [WidgetsBinding.addObserver] and [WidgetsBinding.removeObserver].
23 24 25 26 27 28 29 30 31 32 33 34 35
abstract class WidgetsBindingObserver {
  /// Called when the system tells the app to pop the current route.
  /// For example, on Android, this is called when the user presses
  /// the back button.
  ///
  /// Observers are notified in registration order until one returns
  /// true. If none return true, the application quits.
  ///
  /// Observers are expected to return true if they were able to
  /// handle the notification, for example by closing an active dialog
  /// box, and false otherwise. The [WidgetsApp] widget uses this
  /// mechanism to notify the [Navigator] widget that it should pop
  /// its current route if possible.
Ian Hickson's avatar
Ian Hickson committed
36
  bool didPopRoute() => false;
37 38 39

  /// Called when the application's dimensions change. For example,
  /// when a phone is rotated.
40
  void didChangeMetrics() { }
41 42 43 44

  /// Called when the system tells the app that the user's locale has
  /// changed. For example, if the user changes the system language
  /// settings.
45
  void didChangeLocale(Locale locale) { }
46 47 48

  /// Called when the system puts the app in the background or returns
  /// the app to the foreground.
49
  void didChangeAppLifecycleState(AppLifecycleState state) { }
Ian Hickson's avatar
Ian Hickson committed
50
}
51

52
/// The glue between the widgets layer and the Flutter engine.
53
abstract class WidgetsBinding extends BindingBase implements GestureBinding, RendererBinding {
54
  @override
55
  void initInstances() {
Ian Hickson's avatar
Ian Hickson committed
56 57
    super.initInstances();
    _instance = this;
58
    buildOwner.onBuildScheduled = _handleBuildScheduled;
Ian Hickson's avatar
Ian Hickson committed
59 60
    ui.window.onLocaleChanged = handleLocaleChanged;
    ui.window.onPopRoute = handlePopRoute;
61
    ui.window.onAppLifecycleStateChanged = handleAppLifecycleStateChanged;
62 63
  }

64
  /// The current [WidgetsBinding], if one has been created.
65 66 67
  ///
  /// If you need the binding to be constructed before calling [runApp],
  /// you can ensure a Widget binding has been constructed by calling the
68 69 70
  /// `WidgetsFlutterBinding.ensureInitialized()` function.
  static WidgetsBinding get instance => _instance;
  static WidgetsBinding _instance;
71

72 73 74 75
  @override
  void initServiceExtensions() {
    super.initServiceExtensions();

76 77 78 79 80
    registerSignalServiceExtension(
      name: 'debugDumpApp',
      callback: debugDumpApp
    );

81 82 83 84 85 86 87 88 89 90 91 92
    registerBoolServiceExtension(
      name: 'showPerformanceOverlay',
      getter: () => WidgetsApp.showPerformanceOverlayOverride,
      setter: (bool value) {
        if (WidgetsApp.showPerformanceOverlayOverride == value)
          return;
        WidgetsApp.showPerformanceOverlayOverride = value;
        buildOwner.reassemble(renderViewElement);
      }
    );
  }

93 94 95 96
  /// The [BuildOwner] in charge of executing the build pipeline for the
  /// widget tree rooted at this binding.
  BuildOwner get buildOwner => _buildOwner;
  final BuildOwner _buildOwner = new BuildOwner();
Ian Hickson's avatar
Ian Hickson committed
97

98
  final List<WidgetsBindingObserver> _observers = <WidgetsBindingObserver>[];
Ian Hickson's avatar
Ian Hickson committed
99

100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
  /// Registers the given object as a binding observer. Binding
  /// observers are notified when various application events occur,
  /// for example when the system locale changes. Generally, one
  /// widget in the widget tree registers itself as a binding
  /// observer, and converts the system state into inherited widgets.
  ///
  /// For example, the [WidgetsApp] widget registers as a binding
  /// observer and passes the screen size to a [MediaQuery] widget
  /// each time it is built, which enables other widgets to use the
  /// [MediaQuery.of] static method and (implicitly) the
  /// [InheritedWidget] mechanism to be notified whenever the screen
  /// size changes (e.g. whenever the screen rotates).
  void addObserver(WidgetsBindingObserver observer) => _observers.add(observer);

  /// Unregisters the given observer. This should be used sparingly as
  /// it is relatively expensive (O(N) in the number of registered
  /// observers).
  bool removeObserver(WidgetsBindingObserver observer) => _observers.remove(observer);

119
  /// Called when the system metrics change.
120 121 122 123 124
  ///
  /// Notifies all the observers using
  /// [WidgetsBindingObserver.didChangeMetrics].
  ///
  /// See [ui.window.onMetricsChanged].
125
  @override
Ian Hickson's avatar
Ian Hickson committed
126 127
  void handleMetricsChanged() {
    super.handleMetricsChanged();
128
    for (WidgetsBindingObserver observer in _observers)
129
      observer.didChangeMetrics();
Ian Hickson's avatar
Ian Hickson committed
130 131
  }

132
  /// Called when the system locale changes.
133 134 135 136
  ///
  /// Calls [dispatchLocaleChanged] to notify the binding observers.
  ///
  /// See [ui.window.onLocaleChanged].
Ian Hickson's avatar
Ian Hickson committed
137 138 139 140
  void handleLocaleChanged() {
    dispatchLocaleChanged(ui.window.locale);
  }

141 142 143
  /// Notify all the observers that the locale has changed (using
  /// [WidgetsBindingObserver.didChangeLocale]), giving them the
  /// `locale` argument.
144
  void dispatchLocaleChanged(Locale locale) {
145
    for (WidgetsBindingObserver observer in _observers)
Ian Hickson's avatar
Ian Hickson committed
146 147 148
      observer.didChangeLocale(locale);
  }

149
  /// Called when the system pops the current route.
150 151 152 153 154 155 156 157 158 159 160 161
  ///
  /// This first notifies the binding observers (using
  /// [WidgetsBindingObserver.didPopRoute]), in registration order,
  /// until one returns true, meaning that it was able to handle the
  /// request (e.g. by closing a dialog box). If none return true,
  /// then the application is shut down.
  ///
  /// [WidgetsApp] uses this in conjunction with a [Navigator] to
  /// cause the back button to close dialog boxes, return from modal
  /// pages, and so forth.
  ///
  /// See [ui.window.onPopRoute].
Ian Hickson's avatar
Ian Hickson committed
162
  void handlePopRoute() {
163
    for (WidgetsBindingObserver observer in _observers) {
Ian Hickson's avatar
Ian Hickson committed
164
      if (observer.didPopRoute())
165
        return;
Ian Hickson's avatar
Ian Hickson committed
166
    }
167
    activity.finishCurrentActivity();
Ian Hickson's avatar
Ian Hickson committed
168
  }
Hixie's avatar
Hixie committed
169

170
  /// Called when the application lifecycle state changes.
171 172 173 174 175
  ///
  /// Notifies all the observers using
  /// [WidgetsBindingObserver.didChangeAppLifecycleState].
  ///
  /// See [ui.window.onAppLifecycleStateChanged].
176
  void handleAppLifecycleStateChanged(AppLifecycleState state) {
177
    for (WidgetsBindingObserver observer in _observers)
178 179 180
      observer.didChangeAppLifecycleState(state);
  }

181 182
  bool _needToReportFirstFrame = true;
  bool _thisFrameWasUseful = true;
183

184 185
  /// Tell the framework that the frame we are currently building
  /// should not be considered to be a useful first frame.
186 187
  ///
  /// This is used by [WidgetsApp] to report the first frame.
188 189 190 191
  //
  // TODO(ianh): This method should only be available in debug and profile modes.
  void preventThisFrameFromBeingReportedAsFirstFrame() {
    _thisFrameWasUseful = false;
192 193
  }

194
  void _handleBuildScheduled() {
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
    // If we're in the process of building dirty elements, then changes
    // should not trigger a new frame.
    assert(() {
      if (debugBuildingDirtyElements) {
        throw new FlutterError(
          'Build scheduled during frame.\n'
          'While the widget tree was being built, laid out, and painted, '
          'a new frame was scheduled to rebuild the widget tree. '
          'This might be because setState() was called from a layout or '
          'paint callback. '
          'If a change is needed to the widget tree, it should be applied '
          'as the tree is being built. Scheduling a change for the subsequent '
          'frame instead results in an interface that lags behind by one frame. '
          'If this was done to make your build dependent on a size measured at '
          'layout time, consider using a LayoutBuilder, CustomSingleChildLayout, '
          'or CustomMultiChildLayout. If, on the other hand, the one frame delay '
          'is the desired effect, for example because this is an '
          'animation, consider scheduling the frame in a post-frame callback '
          'using SchedulerBinding.addPostFrameCallback or '
          'using an AnimationController to trigger the animation.'
        );
      }
      return true;
    });
219 220 221
    scheduleFrame();
  }

222 223 224 225 226 227 228 229
  /// Whether we are currently in a frame. This is used to verify
  /// that frames are not scheduled redundantly.
  ///
  /// This is public so that test frameworks can change it.
  ///
  /// This flag is not used in release builds.
  @protected
  bool debugBuildingDirtyElements = false;
230

231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
  /// Pump the build and rendering pipeline to generate a frame.
  ///
  /// This method is called by [handleBeginFrame], which itself is called
  /// automatically by the engine when when it is time to lay out and paint a
  /// frame.
  ///
  /// Each frame consists of the following phases:
  ///
  /// 1. The animation phase: The [handleBeginFrame] method, which is registered
  /// with [ui.window.onBeginFrame], invokes all the transient frame callbacks
  /// registered with [scheduleFrameCallback] and [addFrameCallback], in
  /// registration order. This includes all the [Ticker] instances that are
  /// driving [AnimationController] objects, which means all of the active
  /// [Animation] objects tick at this point.
  ///
  /// [handleBeginFrame] then invokes all the persistent frame callbacks, of which
  /// the most notable is this method, [beginFrame], which proceeds as follows:
  ///
  /// 2. The build phase: All the dirty [Element]s in the widget tree are
  /// rebuilt (see [State.build]). See [State.setState] for further details on
  /// marking a widget dirty for building. See [BuildOwner] for more information
  /// on this step.
  ///
  /// 3. The layout phase: All the dirty [RenderObject]s in the system are laid
  /// out (see [RenderObject.performLayout]). See [RenderObject.markNeedsLayout]
  /// for further details on marking an object dirty for layout.
  ///
  /// 4. The compositing bits phase: The compositing bits on any dirty
  /// [RenderObject] objects are updated. See
  /// [RenderObject.markNeedsCompositingBitsUpdate].
  ///
  /// 5. The paint phase: All the dirty [RenderObject]s in the system are
  /// repainted (see [RenderObject.paint]). This generates the [Layer] tree. See
  /// [RenderObject.markNeedsPaint] for further details on marking an object
  /// dirty for paint.
  ///
  /// 6. The compositing phase: The layer tree is turned into a [ui.Scene] and
  /// sent to the GPU.
  ///
  /// 7. The semantics phase: All the dirty [RenderObject]s in the system have
  /// their semantics updated (see [RenderObject.semanticAnnotator]). This
  /// generates the [SemanticsNode] tree. See
  /// [RenderObject.markNeedsSemanticsUpdate] for further details on marking an
  /// object dirty for semantics.
  ///
  /// For more details on steps 3-7, see [PipelineOwner].
  ///
  /// 8. The finalization phase in the widgets layer: The widgets tree is
  /// finalized. This causes [State.dispose] to be invoked on any objects that
  /// were removed from the widgets tree this frame. See
  /// [BuildOwner.finalizeTree] for more details.
  ///
  /// 9. The finalization phase in the scheduler layer: After [beginFrame]
  /// returns, [handleBeginFrame] then invokes post-frame callbacks (registered
  /// with [addPostFrameCallback].
  //
  // When editing the above, also update rendering/binding.dart's copy.
288
  @override
289
  void beginFrame() {
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
    assert(!debugBuildingDirtyElements);
    assert(() {
      debugBuildingDirtyElements = true;
      return true;
    });
    try {
      buildOwner.buildScope(renderViewElement);
      super.beginFrame();
      buildOwner.finalizeTree();
    } finally {
      assert(() {
        debugBuildingDirtyElements = false;
        return true;
      });
    }
305
    // TODO(ianh): Following code should not be included in release mode, only profile and debug modes.
306
    // See https://github.com/dart-lang/sdk/issues/27192
307
    if (_needToReportFirstFrame) {
308
      if (_thisFrameWasUseful) {
309
        developer.Timeline.instantSync('Widgets completed first useful frame');
310
        developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
311
        _needToReportFirstFrame = false;
312 313
      } else {
        _thisFrameWasUseful = true;
314
      }
315
    }
316
  }
317 318 319

  /// The [Element] that is at the root of the hierarchy (and which wraps the
  /// [RenderView] object at the root of the rendering hierarchy).
320 321
  ///
  /// This is initialized the first time [runApp] is called.
322 323 324 325 326
  Element get renderViewElement => _renderViewElement;
  Element _renderViewElement;
  void _runApp(Widget app) {
    _renderViewElement = new RenderObjectToWidgetAdapter<RenderBox>(
      container: renderView,
327
      debugShortDescription: '[root]',
328
      child: app
329
    ).attachToRenderTree(buildOwner, renderViewElement);
330 331 332 333 334
    assert(() {
      if (debugPrintBeginFrameBanner)
        debugPrint('━━━━━━━┫ Begin Warm-Up Frame ┣━━━━━━━');
      return true;
    });
335
    beginFrame();
336 337 338 339 340
    assert(() {
      if (debugPrintEndFrameBanner)
        debugPrint('━━━━━━━┫ End of Warm-Up Frame ┣━━━━━━━');
      return true;
    });
341
  }
342 343 344

  @override
  void reassembleApplication() {
345 346
    _needToReportFirstFrame = true;
    preventThisFrameFromBeingReportedAsFirstFrame();
347
    buildOwner.reassemble(renderViewElement);
348 349
    super.reassembleApplication();
  }
350
}
Hixie's avatar
Hixie committed
351

352
/// Inflate the given widget and attach it to the screen.
353
///
354
/// Initializes the binding using [WidgetsFlutterBinding] if necessary.
Hixie's avatar
Hixie committed
355
void runApp(Widget app) {
356
  WidgetsFlutterBinding.ensureInitialized()._runApp(app);
Hixie's avatar
Hixie committed
357 358
}

359
/// Print a string representation of the currently running app.
360
void debugDumpApp() {
361 362
  assert(WidgetsBinding.instance != null);
  assert(WidgetsBinding.instance.renderViewElement != null);
Hixie's avatar
Hixie committed
363 364
  String mode = 'RELEASE MODE';
  assert(() { mode = 'CHECKED MODE'; return true; });
365 366
  debugPrint('${WidgetsBinding.instance.runtimeType} - $mode');
  debugPrint(WidgetsBinding.instance.renderViewElement.toStringDeep());
367 368
}

369 370 371 372 373 374 375 376
/// A bridge from a [RenderObject] to an [Element] tree.
///
/// The given container is the [RenderObject] that the [Element] tree should be
/// inserted into. It must be a [RenderObject] that implements the
/// [RenderObjectWithChildMixin] protocol. The type argument `T` is the kind of
/// [RenderObject] that the container expects as its child.
///
/// Used by [runApp] to bootstrap applications.
Hixie's avatar
Hixie committed
377
class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
378 379 380
  /// Creates a bridge from a [RenderObject] to an [Element] tree.
  ///
  /// Used by [WidgetsBinding] to attach the root widget to the [RenderView].
381 382 383 384 385
  RenderObjectToWidgetAdapter({
    this.child,
    RenderObjectWithChildMixin<T> container,
    this.debugShortDescription
  }) : container = container, super(key: new GlobalObjectKey(container));
Hixie's avatar
Hixie committed
386

387
  /// The widget below this widget in the tree.
Hixie's avatar
Hixie committed
388
  final Widget child;
389

390
  /// The [RenderObject] that is the parent of the [Element] created by this widget.
Hixie's avatar
Hixie committed
391
  final RenderObjectWithChildMixin<T> container;
392

393
  /// A short description of this widget used by debugging aids.
394
  final String debugShortDescription;
Hixie's avatar
Hixie committed
395

396
  @override
Hixie's avatar
Hixie committed
397 398
  RenderObjectToWidgetElement<T> createElement() => new RenderObjectToWidgetElement<T>(this);

399
  @override
400
  RenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container;
Hixie's avatar
Hixie committed
401

402
  @override
403
  void updateRenderObject(BuildContext context, RenderObject renderObject) { }
404

405 406
  /// Inflate this widget and actually set the resulting [RenderObject] as the
  /// child of [container].
407 408 409 410 411
  ///
  /// If `element` is null, this function will create a new element. Otherwise,
  /// the given element will be updated with this widget.
  ///
  /// Used by [runApp] to bootstrap applications.
412
  RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement<T> element]) {
413 414
    if (element == null) {
      owner.lockState(() {
415
        element = createElement();
416
        assert(element != null);
417
        element.assignOwner(owner);
418 419
      });
      owner.buildScope(element, () {
420
        element.mount(null, null);
421 422 423
      });
    } else {
      owner.buildScope(element, () {
424
        element.update(this);
425 426
      });
    }
427 428
    return element;
  }
429

430
  @override
431
  String toStringShort() => debugShortDescription ?? super.toStringShort();
Hixie's avatar
Hixie committed
432 433
}

434 435 436 437 438
/// A [RootRenderObjectElement] that is hosted by a [RenderObject].
///
/// This element class is the instantiation of a [RenderObjectToWidgetAdapter]
/// widget. It can be used only as the root of an [Element] tree (it cannot be
/// mounted into another [Element]; it's parent must be null).
Hixie's avatar
Hixie committed
439
///
440 441
/// In typical usage, it will be instantiated for a [RenderObjectToWidgetAdapter]
/// whose container is the [RenderView] that connects to the Flutter engine. In
Hixie's avatar
Hixie committed
442
/// this usage, it is normally instantiated by the bootstrapping logic in the
443
/// [WidgetsFlutterBinding] singleton created by [runApp].
444
class RenderObjectToWidgetElement<T extends RenderObject> extends RootRenderObjectElement {
445 446 447 448 449
  /// Creates an element that is hosted by a [RenderObject].
  ///
  /// The [RenderObject] created by this element is not automatically set as a
  /// child of the hosting [RenderObject]. To actually attach this element to
  /// the render tree, call [RenderObjectToWidgetAdapter.attachToRenderTree].
Hixie's avatar
Hixie committed
450 451
  RenderObjectToWidgetElement(RenderObjectToWidgetAdapter<T> widget) : super(widget);

452
  @override
453 454
  RenderObjectToWidgetAdapter<T> get widget => super.widget;

Hixie's avatar
Hixie committed
455 456
  Element _child;

457
  static const Object _rootChildSlot = const Object();
Hixie's avatar
Hixie committed
458

459
  @override
Hixie's avatar
Hixie committed
460 461 462 463 464
  void visitChildren(ElementVisitor visitor) {
    if (_child != null)
      visitor(_child);
  }

465
  @override
Hixie's avatar
Hixie committed
466
  void mount(Element parent, dynamic newSlot) {
Hixie's avatar
Hixie committed
467
    assert(parent == null);
Hixie's avatar
Hixie committed
468
    super.mount(parent, newSlot);
469
    _rebuild();
Hixie's avatar
Hixie committed
470 471
  }

472
  @override
Hixie's avatar
Hixie committed
473 474 475
  void update(RenderObjectToWidgetAdapter<T> newWidget) {
    super.update(newWidget);
    assert(widget == newWidget);
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492
    _rebuild();
  }

  void _rebuild() {
    try {
      _child = updateChild(_child, widget.child, _rootChildSlot);
      assert(_child != null);
    } catch (exception, stack) {
      FlutterError.reportError(new FlutterErrorDetails(
        exception: exception,
        stack: stack,
        library: 'widgets library',
        context: 'attaching to the render tree'
      ));
      Widget error = new ErrorWidget(exception);
      _child = updateChild(null, error, _rootChildSlot);
    }
Hixie's avatar
Hixie committed
493 494
  }

495
  @override
Hixie's avatar
Hixie committed
496 497
  RenderObjectWithChildMixin<T> get renderObject => super.renderObject;

498
  @override
Hixie's avatar
Hixie committed
499
  void insertChildRenderObject(RenderObject child, dynamic slot) {
Ian Hickson's avatar
Ian Hickson committed
500
    assert(slot == _rootChildSlot);
Hixie's avatar
Hixie committed
501 502 503
    renderObject.child = child;
  }

504
  @override
Adam Barth's avatar
Adam Barth committed
505 506 507 508
  void moveChildRenderObject(RenderObject child, dynamic slot) {
    assert(false);
  }

509
  @override
Hixie's avatar
Hixie committed
510 511 512 513
  void removeChildRenderObject(RenderObject child) {
    assert(renderObject.child == child);
    renderObject.child = null;
  }
Adam Barth's avatar
Adam Barth committed
514
}
515 516 517

/// A concrete binding for applications based on the Widgets framework.
/// This is the glue that binds the framework to the Flutter engine.
518
class WidgetsFlutterBinding extends BindingBase with SchedulerBinding, GestureBinding, ServicesBinding, RendererBinding, WidgetsBinding {
519 520 521 522 523

  /// Returns an instance of the [WidgetsBinding], creating and
  /// initializing it if necessary. If one is created, it will be a
  /// [WidgetsFlutterBinding]. If one was previously initialized, then
  /// it will at least implement [WidgetsBinding].
524 525 526
  ///
  /// You only need to call this method if you need the binding to be
  /// initialized before calling [runApp].
527 528 529 530 531
  ///
  /// In the `flutter_test` framework, [testWidgets] initializes the
  /// binding instance to a [TestWidgetsFlutterBinding], not a
  /// [WidgetsFlutterBinding].
  static WidgetsBinding ensureInitialized() {
532 533 534
    if (WidgetsBinding.instance == null)
      new WidgetsFlutterBinding();
    return WidgetsBinding.instance;
535 536
  }
}