1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | import 'dart:collection'; |
6 | import 'dart:ui' show FlutterView, SemanticsUpdate; |
7 | |
8 | import 'package:flutter/foundation.dart'; |
9 | import 'package:flutter/rendering.dart'; |
10 | |
11 | import 'framework.dart'; |
12 | import 'lookup_boundary.dart'; |
13 | import 'media_query.dart'; |
14 | |
15 | /// Bootstraps a render tree that is rendered into the provided [FlutterView]. |
16 | /// |
17 | /// The content rendered into that view is determined by the provided [child]. |
18 | /// Descendants within the same [LookupBoundary] can look up the view they are |
19 | /// rendered into via [View.of] and [View.maybeOf]. |
20 | /// |
21 | /// The provided [child] is wrapped in a [MediaQuery] constructed from the given |
22 | /// [view]. |
23 | /// |
24 | /// For most use cases, using [MediaQuery.of], or its associated "...Of" methods |
25 | /// are a more appropriate way of obtaining the information that a [FlutterView] |
26 | /// exposes. For example, using [MediaQuery.sizeOf] will expose the _logical_ |
27 | /// device size ([MediaQueryData.size]) rather than the physical size |
28 | /// ([FlutterView.physicalSize]). Similarly, while [FlutterView.padding] conveys |
29 | /// the information from the operating system, the [MediaQueryData.padding] |
30 | /// attribute (obtained from [MediaQuery.paddingOf]) further adjusts this |
31 | /// information to be aware of the context of the widget; e.g. the [Scaffold] |
32 | /// widget adjusts the values for its various children. |
33 | /// |
34 | /// Each [FlutterView] can be associated with at most one [View] widget in the |
35 | /// widget tree. Two or more [View] widgets configured with the same |
36 | /// [FlutterView] must never exist within the same widget tree at the same time. |
37 | /// This limitation is enforced by a [GlobalObjectKey] that derives its identity |
38 | /// from the [view] provided to this widget. |
39 | /// |
40 | /// Since the [View] widget bootstraps its own independent render tree, neither |
41 | /// it nor any of its descendants will insert a [RenderObject] into an existing |
42 | /// render tree. Therefore, the [View] widget can only be used in those parts of |
43 | /// the widget tree where it is not required to participate in the construction |
44 | /// of the surrounding render tree. In other words, the widget may only be used |
45 | /// in a non-rendering zone of the widget tree (see [WidgetsBinding] for a |
46 | /// definition of rendering and non-rendering zones). |
47 | /// |
48 | /// In practical terms, the widget is typically used at the root of the widget |
49 | /// tree outside of any other [View] widget, as a child of a [ViewCollection] |
50 | /// widget, or in the [ViewAnchor.view] slot of a [ViewAnchor] widget. It is not |
51 | /// required to be a direct child, though, since other non-[RenderObjectWidget]s |
52 | /// (e.g. [InheritedWidget]s, [Builder]s, or [StatefulWidget]s/[StatelessWidget] |
53 | /// that only produce non-[RenderObjectWidget]s) are allowed to be present |
54 | /// between those widgets and the [View] widget. |
55 | /// |
56 | /// See also: |
57 | /// |
58 | /// * [Element.debugExpectsRenderObjectForSlot], which defines whether a [View] |
59 | /// widget is allowed in a given child slot. |
60 | class View extends StatelessWidget { |
61 | /// Create a [View] widget to bootstrap a render tree that is rendered into |
62 | /// the provided [FlutterView]. |
63 | /// |
64 | /// The content rendered into that [view] is determined by the given [child] |
65 | /// widget. |
66 | View({ |
67 | super.key, |
68 | required this.view, |
69 | @Deprecated( |
70 | 'Do not use. ' |
71 | 'This parameter only exists to implement the deprecated RendererBinding.pipelineOwner property until it is removed. ' |
72 | 'This feature was deprecated after v3.10.0-12.0.pre.' |
73 | ) |
74 | PipelineOwner? deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner, |
75 | @Deprecated( |
76 | 'Do not use. ' |
77 | 'This parameter only exists to implement the deprecated RendererBinding.renderView property until it is removed. ' |
78 | 'This feature was deprecated after v3.10.0-12.0.pre.' |
79 | ) |
80 | RenderView? deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView, |
81 | required this.child, |
82 | }) : _deprecatedPipelineOwner = deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner, |
83 | _deprecatedRenderView = deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView, |
84 | assert((deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner == null) == (deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView == null)), |
85 | assert(deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView == null || deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView.flutterView == view); |
86 | |
87 | /// The [FlutterView] into which [child] is drawn. |
88 | final FlutterView view; |
89 | |
90 | /// The widget below this widget in the tree, which will be drawn into the |
91 | /// [view]. |
92 | /// |
93 | /// {@macro flutter.widgets.ProxyWidget.child} |
94 | final Widget child; |
95 | |
96 | final PipelineOwner? _deprecatedPipelineOwner; |
97 | final RenderView? _deprecatedRenderView; |
98 | |
99 | /// Returns the [FlutterView] that the provided `context` will render into. |
100 | /// |
101 | /// Returns null if the `context` is not associated with a [FlutterView]. |
102 | /// |
103 | /// The method creates a dependency on the `context`, which will be informed |
104 | /// when the identity of the [FlutterView] changes (i.e. the `context` is |
105 | /// moved to render into a different [FlutterView] then before). The context |
106 | /// will not be informed when the _properties_ on the [FlutterView] itself |
107 | /// change their values. To access the property values of a [FlutterView] it |
108 | /// is best practise to use [MediaQuery.maybeOf] instead, which will ensure |
109 | /// that the `context` is informed when the view properties change. |
110 | /// |
111 | /// See also: |
112 | /// |
113 | /// * [View.of], which throws instead of returning null if no [FlutterView] |
114 | /// is found. |
115 | static FlutterView? maybeOf(BuildContext context) { |
116 | return LookupBoundary.dependOnInheritedWidgetOfExactType<_ViewScope>(context)?.view; |
117 | } |
118 | |
119 | /// Returns the [FlutterView] that the provided `context` will render into. |
120 | /// |
121 | /// Throws if the `context` is not associated with a [FlutterView]. |
122 | /// |
123 | /// The method creates a dependency on the `context`, which will be informed |
124 | /// when the identity of the [FlutterView] changes (i.e. the `context` is |
125 | /// moved to render into a different [FlutterView] then before). The context |
126 | /// will not be informed when the _properties_ on the [FlutterView] itself |
127 | /// change their values. To access the property values of a [FlutterView] |
128 | /// prefer using the access methods on [MediaQuery], such as |
129 | /// [MediaQuery.sizeOf], which will ensure that the `context` is informed when |
130 | /// the view properties change. |
131 | /// |
132 | /// See also: |
133 | /// |
134 | /// * [View.maybeOf], which throws instead of returning null if no |
135 | /// [FlutterView] is found. |
136 | static FlutterView of(BuildContext context) { |
137 | final FlutterView? result = maybeOf(context); |
138 | assert(() { |
139 | if (result == null) { |
140 | final bool hiddenByBoundary = LookupBoundary.debugIsHidingAncestorWidgetOfExactType<_ViewScope>(context); |
141 | final List<DiagnosticsNode> information = <DiagnosticsNode>[ |
142 | if (hiddenByBoundary) ...<DiagnosticsNode>[ |
143 | ErrorSummary('View.of() was called with a context that does not have access to a View widget.' ), |
144 | ErrorDescription('The context provided to View.of() does have a View widget ancestor, but it is hidden by a LookupBoundary.' ), |
145 | ] else ...<DiagnosticsNode>[ |
146 | ErrorSummary('View.of() was called with a context that does not contain a View widget.' ), |
147 | ErrorDescription('No View widget ancestor could be found starting from the context that was passed to View.of().' ), |
148 | ], |
149 | ErrorDescription( |
150 | 'The context used was:\n' |
151 | ' $context' , |
152 | ), |
153 | ErrorHint('This usually means that the provided context is not associated with a View.' ), |
154 | ]; |
155 | throw FlutterError.fromParts(information); |
156 | } |
157 | return true; |
158 | }()); |
159 | return result!; |
160 | } |
161 | |
162 | /// Returns the [PipelineOwner] parent to which a child [View] should attach |
163 | /// its [PipelineOwner] to. |
164 | /// |
165 | /// If `context` has a [View] ancestor, it returns the [PipelineOwner] |
166 | /// responsible for managing the render tree of that view. If there is no |
167 | /// [View] ancestor, [RendererBinding.rootPipelineOwner] is returned instead. |
168 | static PipelineOwner pipelineOwnerOf(BuildContext context) { |
169 | return context.dependOnInheritedWidgetOfExactType<_PipelineOwnerScope>()?.pipelineOwner |
170 | ?? RendererBinding.instance.rootPipelineOwner; |
171 | } |
172 | |
173 | @override |
174 | Widget build(BuildContext context) { |
175 | return _RawView( |
176 | view: view, |
177 | deprecatedPipelineOwner: _deprecatedPipelineOwner, |
178 | deprecatedRenderView: _deprecatedRenderView, |
179 | builder: (BuildContext context, PipelineOwner owner) { |
180 | return _ViewScope( |
181 | view: view, |
182 | child: _PipelineOwnerScope( |
183 | pipelineOwner: owner, |
184 | child: MediaQuery.fromView( |
185 | view: view, |
186 | child: child, |
187 | ), |
188 | ), |
189 | ); |
190 | } |
191 | ); |
192 | } |
193 | } |
194 | |
195 | /// A builder for the content [Widget] of a [_RawView]. |
196 | /// |
197 | /// The widget returned by the builder defines the content that is drawn into |
198 | /// the [FlutterView] configured on the [_RawView]. |
199 | /// |
200 | /// The builder is given the [PipelineOwner] that the [_RawView] uses to manage |
201 | /// its render tree. Typical builder implementations make that pipeline owner |
202 | /// available as an attachment point for potential child views. |
203 | /// |
204 | /// Used by [_RawView.builder]. |
205 | typedef _RawViewContentBuilder = Widget Function(BuildContext context, PipelineOwner owner); |
206 | |
207 | /// The workhorse behind the [View] widget that actually bootstraps a render |
208 | /// tree. |
209 | /// |
210 | /// It instantiates the [RenderView] as the root of that render tree and adds it |
211 | /// to the [RendererBinding] via [RendererBinding.addRenderView]. It also owns |
212 | /// the [PipelineOwner] that manages this render tree and adds it as a child to |
213 | /// the surrounding parent [PipelineOwner] obtained with [View.pipelineOwnerOf]. |
214 | /// This ensures that the render tree bootstrapped by this widget participates |
215 | /// properly in frame production and hit testing. |
216 | class _RawView extends RenderObjectWidget { |
217 | /// Create a [RawView] widget to bootstrap a render tree that is rendered into |
218 | /// the provided [FlutterView]. |
219 | /// |
220 | /// The content rendered into that [view] is determined by the [Widget] |
221 | /// returned by [builder]. |
222 | _RawView({ |
223 | required this.view, |
224 | required PipelineOwner? deprecatedPipelineOwner, |
225 | required RenderView? deprecatedRenderView, |
226 | required this.builder, |
227 | }) : _deprecatedPipelineOwner = deprecatedPipelineOwner, |
228 | _deprecatedRenderView = deprecatedRenderView, |
229 | assert(deprecatedRenderView == null || deprecatedRenderView.flutterView == view), |
230 | // TODO(goderbauer): Replace this with GlobalObjectKey(view) when the deprecated properties are removed. |
231 | super(key: _DeprecatedRawViewKey(view, deprecatedPipelineOwner, deprecatedRenderView)); |
232 | |
233 | /// The [FlutterView] into which the [Widget] returned by [builder] is drawn. |
234 | final FlutterView view; |
235 | |
236 | /// Determines the content [Widget] that is drawn into the [view]. |
237 | /// |
238 | /// The [builder] is given the [PipelineOwner] responsible for the render tree |
239 | /// bootstrapped by this widget and typically makes it available as an |
240 | /// attachment point for potential child views. |
241 | final _RawViewContentBuilder builder; |
242 | |
243 | final PipelineOwner? _deprecatedPipelineOwner; |
244 | final RenderView? _deprecatedRenderView; |
245 | |
246 | @override |
247 | RenderObjectElement createElement() => _RawViewElement(this); |
248 | |
249 | @override |
250 | RenderObject createRenderObject(BuildContext context) { |
251 | return _deprecatedRenderView ?? RenderView( |
252 | view: view, |
253 | ); |
254 | } |
255 | |
256 | // No need to implement updateRenderObject: RawView uses the view as a |
257 | // GlobalKey, so we never need to update the RenderObject with a new view. |
258 | } |
259 | |
260 | class _RawViewElement extends RenderTreeRootElement { |
261 | _RawViewElement(super.widget); |
262 | |
263 | late final PipelineOwner _pipelineOwner = PipelineOwner( |
264 | onSemanticsOwnerCreated: _handleSemanticsOwnerCreated, |
265 | onSemanticsUpdate: _handleSemanticsUpdate, |
266 | onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed, |
267 | ); |
268 | |
269 | PipelineOwner get _effectivePipelineOwner => (widget as _RawView)._deprecatedPipelineOwner ?? _pipelineOwner; |
270 | |
271 | void _handleSemanticsOwnerCreated() { |
272 | (_effectivePipelineOwner.rootNode as RenderView?)?.scheduleInitialSemantics(); |
273 | } |
274 | |
275 | void _handleSemanticsOwnerDisposed() { |
276 | (_effectivePipelineOwner.rootNode as RenderView?)?.clearSemantics(); |
277 | } |
278 | |
279 | void _handleSemanticsUpdate(SemanticsUpdate update) { |
280 | (widget as _RawView).view.updateSemantics(update); |
281 | } |
282 | |
283 | @override |
284 | RenderView get renderObject => super.renderObject as RenderView; |
285 | |
286 | Element? _child; |
287 | |
288 | void _updateChild() { |
289 | try { |
290 | final Widget child = (widget as _RawView).builder(this, _effectivePipelineOwner); |
291 | _child = updateChild(_child, child, null); |
292 | } catch (e, stack) { |
293 | final FlutterErrorDetails details = FlutterErrorDetails( |
294 | exception: e, |
295 | stack: stack, |
296 | library: 'widgets library' , |
297 | context: ErrorDescription('building $this' ), |
298 | informationCollector: !kDebugMode ? null : () => <DiagnosticsNode>[ |
299 | DiagnosticsDebugCreator(DebugCreator(this)), |
300 | ], |
301 | ); |
302 | FlutterError.reportError(details); |
303 | final Widget error = ErrorWidget.builder(details); |
304 | _child = updateChild(null, error, slot); |
305 | } |
306 | } |
307 | |
308 | @override |
309 | void mount(Element? parent, Object? newSlot) { |
310 | super.mount(parent, newSlot); |
311 | assert(_effectivePipelineOwner.rootNode == null); |
312 | _effectivePipelineOwner.rootNode = renderObject; |
313 | _attachView(); |
314 | _updateChild(); |
315 | renderObject.prepareInitialFrame(); |
316 | if (_effectivePipelineOwner.semanticsOwner != null) { |
317 | renderObject.scheduleInitialSemantics(); |
318 | } |
319 | } |
320 | |
321 | PipelineOwner? _parentPipelineOwner; // Is null if view is currently not attached. |
322 | |
323 | void _attachView([PipelineOwner? parentPipelineOwner]) { |
324 | assert(_parentPipelineOwner == null); |
325 | parentPipelineOwner ??= View.pipelineOwnerOf(this); |
326 | parentPipelineOwner.adoptChild(_effectivePipelineOwner); |
327 | RendererBinding.instance.addRenderView(renderObject); |
328 | _parentPipelineOwner = parentPipelineOwner; |
329 | } |
330 | |
331 | void _detachView() { |
332 | final PipelineOwner? parentPipelineOwner = _parentPipelineOwner; |
333 | if (parentPipelineOwner != null) { |
334 | RendererBinding.instance.removeRenderView(renderObject); |
335 | parentPipelineOwner.dropChild(_effectivePipelineOwner); |
336 | _parentPipelineOwner = null; |
337 | } |
338 | } |
339 | |
340 | @override |
341 | void didChangeDependencies() { |
342 | super.didChangeDependencies(); |
343 | if (_parentPipelineOwner == null) { |
344 | return; |
345 | } |
346 | final PipelineOwner newParentPipelineOwner = View.pipelineOwnerOf(this); |
347 | if (newParentPipelineOwner != _parentPipelineOwner) { |
348 | _detachView(); |
349 | _attachView(newParentPipelineOwner); |
350 | } |
351 | } |
352 | |
353 | @override |
354 | void performRebuild() { |
355 | super.performRebuild(); |
356 | _updateChild(); |
357 | } |
358 | |
359 | @override |
360 | void activate() { |
361 | super.activate(); |
362 | assert(_effectivePipelineOwner.rootNode == null); |
363 | _effectivePipelineOwner.rootNode = renderObject; |
364 | _attachView(); |
365 | } |
366 | |
367 | @override |
368 | void deactivate() { |
369 | _detachView(); |
370 | assert(_effectivePipelineOwner.rootNode == renderObject); |
371 | _effectivePipelineOwner.rootNode = null; // To satisfy the assert in the super class. |
372 | super.deactivate(); |
373 | } |
374 | |
375 | @override |
376 | void update(_RawView newWidget) { |
377 | super.update(newWidget); |
378 | _updateChild(); |
379 | } |
380 | |
381 | @override |
382 | void visitChildren(ElementVisitor visitor) { |
383 | if (_child != null) { |
384 | visitor(_child!); |
385 | } |
386 | } |
387 | |
388 | @override |
389 | void forgetChild(Element child) { |
390 | assert(child == _child); |
391 | _child = null; |
392 | super.forgetChild(child); |
393 | } |
394 | |
395 | @override |
396 | void insertRenderObjectChild(RenderBox child, Object? slot) { |
397 | assert(slot == null); |
398 | assert(renderObject.debugValidateChild(child)); |
399 | renderObject.child = child; |
400 | } |
401 | |
402 | @override |
403 | void moveRenderObjectChild(RenderObject child, Object? oldSlot, Object? newSlot) { |
404 | assert(false); |
405 | } |
406 | |
407 | @override |
408 | void removeRenderObjectChild(RenderObject child, Object? slot) { |
409 | assert(slot == null); |
410 | assert(renderObject.child == child); |
411 | renderObject.child = null; |
412 | } |
413 | |
414 | @override |
415 | void unmount() { |
416 | if (_effectivePipelineOwner != (widget as _RawView)._deprecatedPipelineOwner) { |
417 | _effectivePipelineOwner.dispose(); |
418 | } |
419 | super.unmount(); |
420 | } |
421 | } |
422 | |
423 | class _ViewScope extends InheritedWidget { |
424 | const _ViewScope({required this.view, required super.child}); |
425 | |
426 | final FlutterView? view; |
427 | |
428 | @override |
429 | bool updateShouldNotify(_ViewScope oldWidget) => view != oldWidget.view; |
430 | } |
431 | |
432 | class _PipelineOwnerScope extends InheritedWidget { |
433 | const _PipelineOwnerScope({ |
434 | required this.pipelineOwner, |
435 | required super.child, |
436 | }); |
437 | |
438 | final PipelineOwner pipelineOwner; |
439 | |
440 | @override |
441 | bool updateShouldNotify(_PipelineOwnerScope oldWidget) => pipelineOwner != oldWidget.pipelineOwner; |
442 | } |
443 | |
444 | class _MultiChildComponentWidget extends Widget { |
445 | const _MultiChildComponentWidget({ |
446 | super.key, |
447 | List<Widget> views = const <Widget>[], |
448 | Widget? child, |
449 | }) : _views = views, _child = child; |
450 | |
451 | // It is up to the subclasses to make the relevant properties public. |
452 | final List<Widget> _views; |
453 | final Widget? _child; |
454 | |
455 | @override |
456 | Element createElement() => _MultiChildComponentElement(this); |
457 | } |
458 | |
459 | /// A collection of sibling [View]s. |
460 | /// |
461 | /// This widget can only be used in places were a [View] widget is allowed, i.e. |
462 | /// in a non-rendering zone of the widget tree. In practical terms, it can be |
463 | /// used at the root of the widget tree outside of any [View] widget, as a child |
464 | /// to a another [ViewCollection], or in the [ViewAnchor.view] slot of a |
465 | /// [ViewAnchor] widget. It is not required to be a direct child of those |
466 | /// widgets; other non-[RenderObjectWidget]s may appear in between the two (such |
467 | /// as an [InheritedWidget]). |
468 | /// |
469 | /// Similarly, the [views] children of this widget must be [View]s, but they |
470 | /// may be wrapped in additional non-[RenderObjectWidget]s (e.g. |
471 | /// [InheritedWidget]s). |
472 | /// |
473 | /// See also: |
474 | /// |
475 | /// * [WidgetsBinding] for an explanation of rendering and non-rendering zones. |
476 | class ViewCollection extends _MultiChildComponentWidget { |
477 | /// Creates a [ViewCollection] widget. |
478 | const ViewCollection({super.key, required super.views}); |
479 | |
480 | /// The [View] descendants of this widget. |
481 | /// |
482 | /// The [View]s may be wrapped in other non-[RenderObjectWidget]s (e.g. |
483 | /// [InheritedWidget]s). However, no [RenderObjectWidget] is allowed to appear |
484 | /// between the [ViewCollection] and the next [View] widget. |
485 | List<Widget> get views => _views; |
486 | } |
487 | |
488 | /// Decorates a [child] widget with a side [View]. |
489 | /// |
490 | /// This widget must have a [View] ancestor, into which the [child] widget |
491 | /// is rendered. |
492 | /// |
493 | /// Typically, a [View] or [ViewCollection] widget is used in the [view] slot to |
494 | /// define the content of the side view(s). Those widgets may be wrapped in |
495 | /// other non-[RenderObjectWidget]s (e.g. [InheritedWidget]s). However, no |
496 | /// [RenderObjectWidget] is allowed to appear between the [ViewAnchor] and the |
497 | /// next [View] widget in the [view] slot. The widgets in the [view] slot have |
498 | /// access to all [InheritedWidget]s above the [ViewAnchor] in the tree. |
499 | /// |
500 | /// In technical terms, the [ViewAnchor] can only be used in a rendering zone of |
501 | /// the widget tree and the [view] slot marks the start of a new non-rendering |
502 | /// zone (see [WidgetsBinding] for a definition of these zones). Typically, |
503 | /// it is occupied by a [View] widget, which will start a new rendering zone. |
504 | /// |
505 | /// {@template flutter.widgets.ViewAnchor} |
506 | /// An example use case for this widget is a tooltip for a button. The tooltip |
507 | /// should be able to extend beyond the bounds of the main view. For this, the |
508 | /// tooltip can be implemented as a separate [View], which is anchored to the |
509 | /// button in the main view by wrapping that button with a [ViewAnchor]. In this |
510 | /// example, the [view] slot is configured with the tooltip [View] and the |
511 | /// [child] is the button widget rendered into the surrounding view. |
512 | /// {@endtemplate} |
513 | class ViewAnchor extends StatelessWidget { |
514 | /// Creates a [ViewAnchor] widget. |
515 | const ViewAnchor({ |
516 | super.key, |
517 | this.view, |
518 | required this.child, |
519 | }); |
520 | |
521 | /// The widget that defines the view anchored to this widget. |
522 | /// |
523 | /// Typically, a [View] or [ViewCollection] widget is used, which may be |
524 | /// wrapped in other non-[RenderObjectWidget]s (e.g. [InheritedWidget]s). |
525 | /// |
526 | /// {@macro flutter.widgets.ViewAnchor} |
527 | final Widget? view; |
528 | |
529 | /// The widget below this widget in the tree. |
530 | /// |
531 | /// It is rendered into the surrounding view, not in the view defined by |
532 | /// [view]. |
533 | /// |
534 | /// {@macro flutter.widgets.ViewAnchor} |
535 | final Widget child; |
536 | |
537 | @override |
538 | Widget build(BuildContext context) { |
539 | return _MultiChildComponentWidget( |
540 | views: <Widget>[ |
541 | if (view != null) |
542 | _ViewScope( |
543 | view: null, |
544 | child: view!, |
545 | ), |
546 | ], |
547 | child: child, |
548 | ); |
549 | } |
550 | } |
551 | |
552 | class _MultiChildComponentElement extends Element { |
553 | _MultiChildComponentElement(super.widget); |
554 | |
555 | List<Element> _viewElements = <Element>[]; |
556 | final Set<Element> _forgottenViewElements = HashSet<Element>(); |
557 | Element? _childElement; |
558 | |
559 | bool _debugAssertChildren() { |
560 | final _MultiChildComponentWidget typedWidget = widget as _MultiChildComponentWidget; |
561 | // Each view widget must have a corresponding element. |
562 | assert(_viewElements.length == typedWidget._views.length); |
563 | // Iff there is a child widget, it must have a corresponding element. |
564 | assert((_childElement == null) == (typedWidget._child == null)); |
565 | // The child element is not also a view element. |
566 | assert(!_viewElements.contains(_childElement)); |
567 | return true; |
568 | } |
569 | |
570 | @override |
571 | void attachRenderObject(Object? newSlot) { |
572 | super.attachRenderObject(newSlot); |
573 | assert(_debugCheckMustAttachRenderObject(newSlot)); |
574 | } |
575 | |
576 | @override |
577 | void mount(Element? parent, Object? newSlot) { |
578 | super.mount(parent, newSlot); |
579 | assert(_debugCheckMustAttachRenderObject(newSlot)); |
580 | assert(_viewElements.isEmpty); |
581 | assert(_childElement == null); |
582 | rebuild(); |
583 | assert(_debugAssertChildren()); |
584 | } |
585 | |
586 | @override |
587 | void updateSlot(Object? newSlot) { |
588 | super.updateSlot(newSlot); |
589 | assert(_debugCheckMustAttachRenderObject(newSlot)); |
590 | } |
591 | |
592 | bool _debugCheckMustAttachRenderObject(Object? slot) { |
593 | // Check only applies in the ViewCollection configuration. |
594 | if (!kDebugMode || (widget as _MultiChildComponentWidget)._child != null) { |
595 | return true; |
596 | } |
597 | bool hasAncestorRenderObjectElement = false; |
598 | bool ancestorWantsRenderObject = true; |
599 | visitAncestorElements((Element ancestor) { |
600 | if (!ancestor.debugExpectsRenderObjectForSlot(slot)) { |
601 | ancestorWantsRenderObject = false; |
602 | return false; |
603 | } |
604 | if (ancestor is RenderObjectElement) { |
605 | hasAncestorRenderObjectElement = true; |
606 | return false; |
607 | } |
608 | return true; |
609 | }); |
610 | if (hasAncestorRenderObjectElement && ancestorWantsRenderObject) { |
611 | FlutterError.reportError( |
612 | FlutterErrorDetails(exception: FlutterError.fromParts( |
613 | <DiagnosticsNode>[ |
614 | ErrorSummary( |
615 | 'The Element for ${toStringShort()} cannot be inserted into slot " $slot" of its ancestor. ' , |
616 | ), |
617 | ErrorDescription( |
618 | 'The ownership chain for the Element in question was:\n ${debugGetCreatorChain(10)}' , |
619 | ), |
620 | ErrorDescription( |
621 | 'This Element allows the creation of multiple independent render trees, which cannot ' |
622 | 'be attached to an ancestor in an existing render tree. However, an ancestor RenderObject ' |
623 | 'is expecting that a child will be attached.' |
624 | ), |
625 | ErrorHint( |
626 | 'Try moving the subtree that contains the ${toStringShort()} widget into the ' |
627 | 'view property of a ViewAnchor widget or to the root of the widget tree, where ' |
628 | 'it is not expected to attach its RenderObject to its ancestor.' , |
629 | ), |
630 | ], |
631 | )), |
632 | ); |
633 | } |
634 | return true; |
635 | } |
636 | |
637 | @override |
638 | void update(_MultiChildComponentWidget newWidget) { |
639 | // Cannot switch from ViewAnchor config to ViewCollection config. |
640 | assert((newWidget._child == null) == ((widget as _MultiChildComponentWidget)._child == null)); |
641 | super.update(newWidget); |
642 | rebuild(force: true); |
643 | assert(_debugAssertChildren()); |
644 | } |
645 | |
646 | static const Object _viewSlot = Object(); |
647 | |
648 | @override |
649 | bool debugExpectsRenderObjectForSlot(Object? slot) => slot != _viewSlot; |
650 | |
651 | @override |
652 | void performRebuild() { |
653 | final _MultiChildComponentWidget typedWidget = widget as _MultiChildComponentWidget; |
654 | |
655 | _childElement = updateChild(_childElement, typedWidget._child, slot); |
656 | |
657 | final List<Widget> views = typedWidget._views; |
658 | _viewElements = updateChildren( |
659 | _viewElements, |
660 | views, |
661 | forgottenChildren: _forgottenViewElements, |
662 | slots: List<Object>.generate(views.length, (_) => _viewSlot), |
663 | ); |
664 | _forgottenViewElements.clear(); |
665 | |
666 | super.performRebuild(); // clears the dirty flag |
667 | assert(_debugAssertChildren()); |
668 | } |
669 | |
670 | @override |
671 | void forgetChild(Element child) { |
672 | if (child == _childElement) { |
673 | _childElement = null; |
674 | } else { |
675 | assert(_viewElements.contains(child)); |
676 | assert(!_forgottenViewElements.contains(child)); |
677 | _forgottenViewElements.add(child); |
678 | } |
679 | super.forgetChild(child); |
680 | } |
681 | |
682 | @override |
683 | void visitChildren(ElementVisitor visitor) { |
684 | if (_childElement != null) { |
685 | visitor(_childElement!); |
686 | } |
687 | for (final Element child in _viewElements) { |
688 | if (!_forgottenViewElements.contains(child)) { |
689 | visitor(child); |
690 | } |
691 | } |
692 | } |
693 | |
694 | @override |
695 | bool get debugDoingBuild => false; // This element does not have a concept of "building". |
696 | |
697 | @override |
698 | Element? get renderObjectAttachingChild => _childElement; |
699 | |
700 | @override |
701 | List<DiagnosticsNode> debugDescribeChildren() { |
702 | final List<DiagnosticsNode> children = <DiagnosticsNode>[]; |
703 | if (_childElement != null) { |
704 | children.add(_childElement!.toDiagnosticsNode()); |
705 | } |
706 | for (int i = 0; i < _viewElements.length; i++) { |
707 | children.add(_viewElements[i].toDiagnosticsNode( |
708 | name: 'view ${i + 1}' , |
709 | style: DiagnosticsTreeStyle.offstage, |
710 | )); |
711 | } |
712 | return children; |
713 | } |
714 | } |
715 | |
716 | // A special [GlobalKey] to support passing the deprecated |
717 | // [RendererBinding.renderView] and [RendererBinding.pipelineOwner] to the |
718 | // [_RawView]. Will be removed when those deprecated properties are removed. |
719 | @optionalTypeArgs |
720 | class _DeprecatedRawViewKey<T extends State<StatefulWidget>> extends GlobalKey<T> { |
721 | const _DeprecatedRawViewKey(this.view, this.owner, this.renderView) : super.constructor(); |
722 | |
723 | final FlutterView view; |
724 | final PipelineOwner? owner; |
725 | final RenderView? renderView; |
726 | |
727 | @override |
728 | bool operator ==(Object other) { |
729 | if (other.runtimeType != runtimeType) { |
730 | return false; |
731 | } |
732 | return other is _DeprecatedRawViewKey<T> |
733 | && identical(other.view, view) |
734 | && identical(other.owner, owner) |
735 | && identical(other.renderView, renderView); |
736 | } |
737 | |
738 | @override |
739 | int get hashCode => Object.hash(view, owner, renderView); |
740 | |
741 | @override |
742 | String toString() => '[_DeprecatedRawViewKey ${describeIdentity(view)}]' ; |
743 | } |
744 | |