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
5import 'dart:collection';
6import 'dart:ui' show FlutterView, SemanticsUpdate;
7
8import 'package:flutter/foundation.dart';
9import 'package:flutter/rendering.dart';
10
11import 'framework.dart';
12import 'lookup_boundary.dart';
13import '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.
60class 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].
205typedef _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.
216class _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
260class _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
423class _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
432class _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
444class _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.
476class 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}
513class 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
552class _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
720class _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