binding.dart 12.9 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

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

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

19 20
/// Interface for classes that register with the Widgets layer binding.
///
21
/// See [WidgetsBinding.addObserver] and [WidgetsBinding.removeObserver].
22 23 24 25 26 27 28 29 30 31 32 33 34
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
35
  bool didPopRoute() => false;
36 37 38

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

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

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

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

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

71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
  @override
  void initServiceExtensions() {
    super.initServiceExtensions();

    registerBoolServiceExtension(
      name: 'showPerformanceOverlay',
      getter: () => WidgetsApp.showPerformanceOverlayOverride,
      setter: (bool value) {
        if (WidgetsApp.showPerformanceOverlayOverride == value)
          return;
        WidgetsApp.showPerformanceOverlayOverride = value;
        buildOwner.reassemble(renderViewElement);
      }
    );
  }

87 88 89 90
  /// 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
91

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

94 95 96 97 98 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);

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

126 127 128 129 130
  /// Invoked when the system locale changes.
  ///
  /// Calls [dispatchLocaleChanged] to notify the binding observers.
  ///
  /// See [ui.window.onLocaleChanged].
Ian Hickson's avatar
Ian Hickson committed
131 132 133 134
  void handleLocaleChanged() {
    dispatchLocaleChanged(ui.window.locale);
  }

135 136 137
  /// Notify all the observers that the locale has changed (using
  /// [WidgetsBindingObserver.didChangeLocale]), giving them the
  /// `locale` argument.
138
  void dispatchLocaleChanged(Locale locale) {
139
    for (WidgetsBindingObserver observer in _observers)
Ian Hickson's avatar
Ian Hickson committed
140 141 142
      observer.didChangeLocale(locale);
  }

143 144 145 146 147 148 149 150 151 152 153 154 155
  /// Invoked when the system pops the current route.
  ///
  /// 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
156
  void handlePopRoute() {
157
    for (WidgetsBindingObserver observer in _observers) {
Ian Hickson's avatar
Ian Hickson committed
158
      if (observer.didPopRoute())
159
        return;
Ian Hickson's avatar
Ian Hickson committed
160
    }
161
    activity.finishCurrentActivity();
Ian Hickson's avatar
Ian Hickson committed
162
  }
Hixie's avatar
Hixie committed
163

164 165 166 167 168 169
  /// Invoked when the application lifecycle state changes.
  ///
  /// Notifies all the observers using
  /// [WidgetsBindingObserver.didChangeAppLifecycleState].
  ///
  /// See [ui.window.onAppLifecycleStateChanged].
170
  void handleAppLifecycleStateChanged(AppLifecycleState state) {
171
    for (WidgetsBindingObserver observer in _observers)
172 173 174
      observer.didChangeAppLifecycleState(state);
  }

175 176 177 178 179 180 181 182 183 184 185
  bool _didFirstFrame = false;
  bool _reportFirstFrame = true;

  /// Tell the framework that the first useful frame has been completed.
  ///
  /// This is used by [WidgetsApp] to report the first frame.
  // remove this once we've fixed https://github.com/flutter/flutter/issues/1865
  void didFirstFrame() {
    _didFirstFrame = true;
  }

186 187 188 189 190 191 192 193 194 195 196
  void _handleBuildScheduled() {
    // If we're in the process of building dirty elements, we're know that any
    // builds that are scheduled will be run this frame, which means we don't
    // need to schedule another frame.
    if (_buildingDirtyElements)
      return;
    scheduleFrame();
  }

  bool _buildingDirtyElements = false;

197
  @override
198
  void beginFrame() {
199 200
    assert(!_buildingDirtyElements);
    _buildingDirtyElements = true;
201
    buildOwner.buildDirtyElements();
202
    _buildingDirtyElements = false;
203
    super.beginFrame();
204
    buildOwner.finalizeTree();
205 206 207 208 209
    // TODO(ianh): Following code should not be included in release mode, only profile and debug mode
    if (_reportFirstFrame && _didFirstFrame) {
      developer.Timeline.instantSync('Widgets completed first useful frame');
      _reportFirstFrame = false;
    }
210
  }
211 212 213

  /// The [Element] that is at the root of the hierarchy (and which wraps the
  /// [RenderView] object at the root of the rendering hierarchy).
214 215
  ///
  /// This is initialized the first time [runApp] is called.
216 217 218 219 220
  Element get renderViewElement => _renderViewElement;
  Element _renderViewElement;
  void _runApp(Widget app) {
    _renderViewElement = new RenderObjectToWidgetAdapter<RenderBox>(
      container: renderView,
221
      debugShortDescription: '[root]',
222
      child: app
223
    ).attachToRenderTree(buildOwner, renderViewElement);
224
    beginFrame();
225
  }
226 227 228

  @override
  void reassembleApplication() {
229
    buildOwner.reassemble(renderViewElement);
230 231
    super.reassembleApplication();
  }
232
}
Hixie's avatar
Hixie committed
233

234
/// Inflate the given widget and attach it to the screen.
235
///
236
/// Initializes the binding using [WidgetsFlutterBinding] if necessary.
Hixie's avatar
Hixie committed
237
void runApp(Widget app) {
238
  WidgetsFlutterBinding.ensureInitialized()._runApp(app);
Hixie's avatar
Hixie committed
239 240
}

241
/// Print a string representation of the currently running app.
242
void debugDumpApp() {
243 244
  assert(WidgetsBinding.instance != null);
  assert(WidgetsBinding.instance.renderViewElement != null);
Hixie's avatar
Hixie committed
245 246
  String mode = 'RELEASE MODE';
  assert(() { mode = 'CHECKED MODE'; return true; });
247 248
  debugPrint('${WidgetsBinding.instance.runtimeType} - $mode');
  debugPrint(WidgetsBinding.instance.renderViewElement.toStringDeep());
249 250
}

Hixie's avatar
Hixie committed
251 252 253 254 255 256
/// This class provides 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.
class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
257 258 259 260 261
  RenderObjectToWidgetAdapter({
    this.child,
    RenderObjectWithChildMixin<T> container,
    this.debugShortDescription
  }) : container = container, super(key: new GlobalObjectKey(container));
Hixie's avatar
Hixie committed
262

263
  /// The widget below this widget in the tree.
Hixie's avatar
Hixie committed
264
  final Widget child;
265

Hixie's avatar
Hixie committed
266
  final RenderObjectWithChildMixin<T> container;
267

268
  final String debugShortDescription;
Hixie's avatar
Hixie committed
269

270
  @override
Hixie's avatar
Hixie committed
271 272
  RenderObjectToWidgetElement<T> createElement() => new RenderObjectToWidgetElement<T>(this);

273
  @override
274
  RenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container;
Hixie's avatar
Hixie committed
275

276
  @override
277
  void updateRenderObject(BuildContext context, RenderObject renderObject) { }
278

279 280
  RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement<T> element]) {
    owner.lockState(() {
281 282
      if (element == null) {
        element = createElement();
283
        element.assignOwner(owner);
284 285 286 287
        element.mount(null, null);
      } else {
        element.update(this);
      }
288
    }, building: true);
289 290
    return element;
  }
291

292
  @override
293
  String toStringShort() => debugShortDescription ?? super.toStringShort();
Hixie's avatar
Hixie committed
294 295 296 297 298 299 300 301 302
}

/// This element class is the instantiation of a [RenderObjectToWidgetAdapter].
/// It can only be used as the root of an Element tree (it cannot be mounted
/// into another Element, it's parent must be null).
///
/// In typical usage, it will be instantiated for a RenderObjectToWidgetAdapter
/// whose container is the RenderView that connects to the Flutter engine. In
/// this usage, it is normally instantiated by the bootstrapping logic in the
303
/// WidgetsFlutterBinding singleton created by runApp().
304
class RenderObjectToWidgetElement<T extends RenderObject> extends RootRenderObjectElement {
Hixie's avatar
Hixie committed
305 306
  RenderObjectToWidgetElement(RenderObjectToWidgetAdapter<T> widget) : super(widget);

307
  @override
308 309
  RenderObjectToWidgetAdapter<T> get widget => super.widget;

Hixie's avatar
Hixie committed
310 311
  Element _child;

312
  static const Object _rootChildSlot = const Object();
Hixie's avatar
Hixie committed
313

314
  @override
Hixie's avatar
Hixie committed
315 316 317 318 319
  void visitChildren(ElementVisitor visitor) {
    if (_child != null)
      visitor(_child);
  }

320
  @override
Hixie's avatar
Hixie committed
321
  void mount(Element parent, dynamic newSlot) {
Hixie's avatar
Hixie committed
322
    assert(parent == null);
Hixie's avatar
Hixie committed
323
    super.mount(parent, newSlot);
Ian Hickson's avatar
Ian Hickson committed
324
    _child = updateChild(_child, widget.child, _rootChildSlot);
Hixie's avatar
Hixie committed
325 326
  }

327
  @override
Hixie's avatar
Hixie committed
328 329 330
  void update(RenderObjectToWidgetAdapter<T> newWidget) {
    super.update(newWidget);
    assert(widget == newWidget);
Ian Hickson's avatar
Ian Hickson committed
331
    _child = updateChild(_child, widget.child, _rootChildSlot);
Hixie's avatar
Hixie committed
332 333
  }

334
  @override
Hixie's avatar
Hixie committed
335 336
  RenderObjectWithChildMixin<T> get renderObject => super.renderObject;

337
  @override
Hixie's avatar
Hixie committed
338
  void insertChildRenderObject(RenderObject child, dynamic slot) {
Ian Hickson's avatar
Ian Hickson committed
339
    assert(slot == _rootChildSlot);
Hixie's avatar
Hixie committed
340 341 342
    renderObject.child = child;
  }

343
  @override
Adam Barth's avatar
Adam Barth committed
344 345 346 347
  void moveChildRenderObject(RenderObject child, dynamic slot) {
    assert(false);
  }

348
  @override
Hixie's avatar
Hixie committed
349 350 351 352
  void removeChildRenderObject(RenderObject child) {
    assert(renderObject.child == child);
    renderObject.child = null;
  }
Adam Barth's avatar
Adam Barth committed
353
}
354 355 356

/// A concrete binding for applications based on the Widgets framework.
/// This is the glue that binds the framework to the Flutter engine.
357 358
class WidgetsFlutterBinding extends BindingBase with SchedulerBinding, GestureBinding, ServicesBinding, RendererBinding, WidgetsBinding {
  /// Creates and initializes the WidgetsFlutterBinding. This function
359 360 361 362 363
  /// is idempotent; calling it a second time will just return the
  /// previously-created instance.
  ///
  /// You only need to call this method if you need the binding to be
  /// initialized before calling [runApp].
364 365 366 367
  static WidgetsFlutterBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      new WidgetsFlutterBinding();
    return WidgetsBinding.instance;
368 369
  }
}