blob: 728a09d469737431922e6f0ba67565e815894425 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:collection';
import 'dart:ui' show FlutterView, SemanticsUpdate;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'framework.dart';
import 'lookup_boundary.dart';
import 'media_query.dart';
/// Bootstraps a render tree that is rendered into the provided [FlutterView].
///
/// The content rendered into that view is determined by the provided [child].
/// Descendants within the same [LookupBoundary] can look up the view they are
/// rendered into via [View.of] and [View.maybeOf].
///
/// The provided [child] is wrapped in a [MediaQuery] constructed from the given
/// [view].
///
/// For most use cases, using [MediaQuery.of], or its associated "...Of" methods
/// are a more appropriate way of obtaining the information that a [FlutterView]
/// exposes. For example, using [MediaQuery.sizeOf] will expose the _logical_
/// device size ([MediaQueryData.size]) rather than the physical size
/// ([FlutterView.physicalSize]). Similarly, while [FlutterView.padding] conveys
/// the information from the operating system, the [MediaQueryData.padding]
/// attribute (obtained from [MediaQuery.paddingOf]) further adjusts this
/// information to be aware of the context of the widget; e.g. the [Scaffold]
/// widget adjusts the values for its various children.
///
/// Each [FlutterView] can be associated with at most one [View] widget in the
/// widget tree. Two or more [View] widgets configured with the same
/// [FlutterView] must never exist within the same widget tree at the same time.
/// This limitation is enforced by a [GlobalObjectKey] that derives its identity
/// from the [view] provided to this widget.
///
/// Since the [View] widget bootstraps its own independent render tree, neither
/// it nor any of its descendants will insert a [RenderObject] into an existing
/// render tree. Therefore, the [View] widget can only be used in those parts of
/// the widget tree where it is not required to participate in the construction
/// of the surrounding render tree. In other words, the widget may only be used
/// in a non-rendering zone of the widget tree (see [WidgetsBinding] for a
/// definition of rendering and non-rendering zones).
///
/// In practical terms, the widget is typically used at the root of the widget
/// tree outside of any other [View] widget, as a child of a [ViewCollection]
/// widget, or in the [ViewAnchor.view] slot of a [ViewAnchor] widget. It is not
/// required to be a direct child, though, since other non-[RenderObjectWidget]s
/// (e.g. [InheritedWidget]s, [Builder]s, or [StatefulWidget]s/[StatelessWidget]
/// that only produce non-[RenderObjectWidget]s) are allowed to be present
/// between those widgets and the [View] widget.
///
/// See also:
///
/// * [Element.debugExpectsRenderObjectForSlot], which defines whether a [View]
/// widget is allowed in a given child slot.
class View extends StatelessWidget {
/// Create a [View] widget to bootstrap a render tree that is rendered into
/// the provided [FlutterView].
///
/// The content rendered into that [view] is determined by the given [child]
/// widget.
View({
super.key,
required this.view,
@Deprecated(
'Do not use. '
'This parameter only exists to implement the deprecated RendererBinding.pipelineOwner property until it is removed. '
'This feature was deprecated after v3.10.0-12.0.pre.'
)
PipelineOwner? deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner,
@Deprecated(
'Do not use. '
'This parameter only exists to implement the deprecated RendererBinding.renderView property until it is removed. '
'This feature was deprecated after v3.10.0-12.0.pre.'
)
RenderView? deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView,
required this.child,
}) : _deprecatedPipelineOwner = deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner,
_deprecatedRenderView = deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView,
assert((deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner == null) == (deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView == null)),
assert(deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView == null || deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView.flutterView == view);
/// The [FlutterView] into which [child] is drawn.
final FlutterView view;
/// The widget below this widget in the tree, which will be drawn into the
/// [view].
///
/// {@macro flutter.widgets.ProxyWidget.child}
final Widget child;
final PipelineOwner? _deprecatedPipelineOwner;
final RenderView? _deprecatedRenderView;
/// Returns the [FlutterView] that the provided `context` will render into.
///
/// Returns null if the `context` is not associated with a [FlutterView].
///
/// The method creates a dependency on the `context`, which will be informed
/// when the identity of the [FlutterView] changes (i.e. the `context` is
/// moved to render into a different [FlutterView] then before). The context
/// will not be informed when the _properties_ on the [FlutterView] itself
/// change their values. To access the property values of a [FlutterView] it
/// is best practise to use [MediaQuery.maybeOf] instead, which will ensure
/// that the `context` is informed when the view properties change.
///
/// See also:
///
/// * [View.of], which throws instead of returning null if no [FlutterView]
/// is found.
static FlutterView? maybeOf(BuildContext context) {
return LookupBoundary.dependOnInheritedWidgetOfExactType<_ViewScope>(context)?.view;
}
/// Returns the [FlutterView] that the provided `context` will render into.
///
/// Throws if the `context` is not associated with a [FlutterView].
///
/// The method creates a dependency on the `context`, which will be informed
/// when the identity of the [FlutterView] changes (i.e. the `context` is
/// moved to render into a different [FlutterView] then before). The context
/// will not be informed when the _properties_ on the [FlutterView] itself
/// change their values. To access the property values of a [FlutterView]
/// prefer using the access methods on [MediaQuery], such as
/// [MediaQuery.sizeOf], which will ensure that the `context` is informed when
/// the view properties change.
///
/// See also:
///
/// * [View.maybeOf], which throws instead of returning null if no
/// [FlutterView] is found.
static FlutterView of(BuildContext context) {
final FlutterView? result = maybeOf(context);
assert(() {
if (result == null) {
final bool hiddenByBoundary = LookupBoundary.debugIsHidingAncestorWidgetOfExactType<_ViewScope>(context);
final List<DiagnosticsNode> information = <DiagnosticsNode>[
if (hiddenByBoundary) ...<DiagnosticsNode>[
ErrorSummary('View.of() was called with a context that does not have access to a View widget.'),
ErrorDescription('The context provided to View.of() does have a View widget ancestor, but it is hidden by a LookupBoundary.'),
] else ...<DiagnosticsNode>[
ErrorSummary('View.of() was called with a context that does not contain a View widget.'),
ErrorDescription('No View widget ancestor could be found starting from the context that was passed to View.of().'),
],
ErrorDescription(
'The context used was:\n'
' $context',
),
ErrorHint('This usually means that the provided context is not associated with a View.'),
];
throw FlutterError.fromParts(information);
}
return true;
}());
return result!;
}
/// Returns the [PipelineOwner] parent to which a child [View] should attach
/// its [PipelineOwner] to.
///
/// If `context` has a [View] ancestor, it returns the [PipelineOwner]
/// responsible for managing the render tree of that view. If there is no
/// [View] ancestor, [RendererBinding.rootPipelineOwner] is returned instead.
static PipelineOwner pipelineOwnerOf(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<_PipelineOwnerScope>()?.pipelineOwner
?? RendererBinding.instance.rootPipelineOwner;
}
@override
Widget build(BuildContext context) {
return _RawView(
view: view,
deprecatedPipelineOwner: _deprecatedPipelineOwner,
deprecatedRenderView: _deprecatedRenderView,
builder: (BuildContext context, PipelineOwner owner) {
return _ViewScope(
view: view,
child: _PipelineOwnerScope(
pipelineOwner: owner,
child: MediaQuery.fromView(
view: view,
child: child,
),
),
);
}
);
}
}
/// A builder for the content [Widget] of a [_RawView].
///
/// The widget returned by the builder defines the content that is drawn into
/// the [FlutterView] configured on the [_RawView].
///
/// The builder is given the [PipelineOwner] that the [_RawView] uses to manage
/// its render tree. Typical builder implementations make that pipeline owner
/// available as an attachment point for potential child views.
///
/// Used by [_RawView.builder].
typedef _RawViewContentBuilder = Widget Function(BuildContext context, PipelineOwner owner);
/// The workhorse behind the [View] widget that actually bootstraps a render
/// tree.
///
/// It instantiates the [RenderView] as the root of that render tree and adds it
/// to the [RendererBinding] via [RendererBinding.addRenderView]. It also owns
/// the [PipelineOwner] that manages this render tree and adds it as a child to
/// the surrounding parent [PipelineOwner] obtained with [View.pipelineOwnerOf].
/// This ensures that the render tree bootstrapped by this widget participates
/// properly in frame production and hit testing.
class _RawView extends RenderObjectWidget {
/// Create a [RawView] widget to bootstrap a render tree that is rendered into
/// the provided [FlutterView].
///
/// The content rendered into that [view] is determined by the [Widget]
/// returned by [builder].
_RawView({
required this.view,
required PipelineOwner? deprecatedPipelineOwner,
required RenderView? deprecatedRenderView,
required this.builder,
}) : _deprecatedPipelineOwner = deprecatedPipelineOwner,
_deprecatedRenderView = deprecatedRenderView,
assert(deprecatedRenderView == null || deprecatedRenderView.flutterView == view),
// TODO(goderbauer): Replace this with GlobalObjectKey(view) when the deprecated properties are removed.
super(key: _DeprecatedRawViewKey(view, deprecatedPipelineOwner, deprecatedRenderView));
/// The [FlutterView] into which the [Widget] returned by [builder] is drawn.
final FlutterView view;
/// Determines the content [Widget] that is drawn into the [view].
///
/// The [builder] is given the [PipelineOwner] responsible for the render tree
/// bootstrapped by this widget and typically makes it available as an
/// attachment point for potential child views.
final _RawViewContentBuilder builder;
final PipelineOwner? _deprecatedPipelineOwner;
final RenderView? _deprecatedRenderView;
@override
RenderObjectElement createElement() => _RawViewElement(this);
@override
RenderObject createRenderObject(BuildContext context) {
return _deprecatedRenderView ?? RenderView(
view: view,
);
}
// No need to implement updateRenderObject: RawView uses the view as a
// GlobalKey, so we never need to update the RenderObject with a new view.
}
class _RawViewElement extends RenderTreeRootElement {
_RawViewElement(super.widget);
late final PipelineOwner _pipelineOwner = PipelineOwner(
onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
onSemanticsUpdate: _handleSemanticsUpdate,
onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
);
PipelineOwner get _effectivePipelineOwner => (widget as _RawView)._deprecatedPipelineOwner ?? _pipelineOwner;
void _handleSemanticsOwnerCreated() {
(_effectivePipelineOwner.rootNode as RenderView?)?.scheduleInitialSemantics();
}
void _handleSemanticsOwnerDisposed() {
(_effectivePipelineOwner.rootNode as RenderView?)?.clearSemantics();
}
void _handleSemanticsUpdate(SemanticsUpdate update) {
(widget as _RawView).view.updateSemantics(update);
}
@override
RenderView get renderObject => super.renderObject as RenderView;
Element? _child;
void _updateChild() {
try {
final Widget child = (widget as _RawView).builder(this, _effectivePipelineOwner);
_child = updateChild(_child, child, null);
} catch (e, stack) {
final FlutterErrorDetails details = FlutterErrorDetails(
exception: e,
stack: stack,
library: 'widgets library',
context: ErrorDescription('building $this'),
informationCollector: !kDebugMode ? null : () => <DiagnosticsNode>[
DiagnosticsDebugCreator(DebugCreator(this)),
],
);
FlutterError.reportError(details);
final Widget error = ErrorWidget.builder(details);
_child = updateChild(null, error, slot);
}
}
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
assert(_effectivePipelineOwner.rootNode == null);
_effectivePipelineOwner.rootNode = renderObject;
_attachView();
_updateChild();
renderObject.prepareInitialFrame();
if (_effectivePipelineOwner.semanticsOwner != null) {
renderObject.scheduleInitialSemantics();
}
}
PipelineOwner? _parentPipelineOwner; // Is null if view is currently not attached.
void _attachView([PipelineOwner? parentPipelineOwner]) {
assert(_parentPipelineOwner == null);
parentPipelineOwner ??= View.pipelineOwnerOf(this);
parentPipelineOwner.adoptChild(_effectivePipelineOwner);
RendererBinding.instance.addRenderView(renderObject);
_parentPipelineOwner = parentPipelineOwner;
}
void _detachView() {
final PipelineOwner? parentPipelineOwner = _parentPipelineOwner;
if (parentPipelineOwner != null) {
RendererBinding.instance.removeRenderView(renderObject);
parentPipelineOwner.dropChild(_effectivePipelineOwner);
_parentPipelineOwner = null;
}
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (_parentPipelineOwner == null) {
return;
}
final PipelineOwner newParentPipelineOwner = View.pipelineOwnerOf(this);
if (newParentPipelineOwner != _parentPipelineOwner) {
_detachView();
_attachView(newParentPipelineOwner);
}
}
@override
void performRebuild() {
super.performRebuild();
_updateChild();
}
@override
void activate() {
super.activate();
assert(_effectivePipelineOwner.rootNode == null);
_effectivePipelineOwner.rootNode = renderObject;
_attachView();
}
@override
void deactivate() {
_detachView();
assert(_effectivePipelineOwner.rootNode == renderObject);
_effectivePipelineOwner.rootNode = null; // To satisfy the assert in the super class.
super.deactivate();
}
@override
void update(_RawView newWidget) {
super.update(newWidget);
_updateChild();
}
@override
void visitChildren(ElementVisitor visitor) {
if (_child != null) {
visitor(_child!);
}
}
@override
void forgetChild(Element child) {
assert(child == _child);
_child = null;
super.forgetChild(child);
}
@override
void insertRenderObjectChild(RenderBox child, Object? slot) {
assert(slot == null);
assert(renderObject.debugValidateChild(child));
renderObject.child = child;
}
@override
void moveRenderObjectChild(RenderObject child, Object? oldSlot, Object? newSlot) {
assert(false);
}
@override
void removeRenderObjectChild(RenderObject child, Object? slot) {
assert(slot == null);
assert(renderObject.child == child);
renderObject.child = null;
}
@override
void unmount() {
if (_effectivePipelineOwner != (widget as _RawView)._deprecatedPipelineOwner) {
_effectivePipelineOwner.dispose();
}
super.unmount();
}
}
class _ViewScope extends InheritedWidget {
const _ViewScope({required this.view, required super.child});
final FlutterView? view;
@override
bool updateShouldNotify(_ViewScope oldWidget) => view != oldWidget.view;
}
class _PipelineOwnerScope extends InheritedWidget {
const _PipelineOwnerScope({
required this.pipelineOwner,
required super.child,
});
final PipelineOwner pipelineOwner;
@override
bool updateShouldNotify(_PipelineOwnerScope oldWidget) => pipelineOwner != oldWidget.pipelineOwner;
}
class _MultiChildComponentWidget extends Widget {
const _MultiChildComponentWidget({
super.key,
List<Widget> views = const <Widget>[],
Widget? child,
}) : _views = views, _child = child;
// It is up to the subclasses to make the relevant properties public.
final List<Widget> _views;
final Widget? _child;
@override
Element createElement() => _MultiChildComponentElement(this);
}
/// A collection of sibling [View]s.
///
/// This widget can only be used in places were a [View] widget is allowed, i.e.
/// in a non-rendering zone of the widget tree. In practical terms, it can be
/// used at the root of the widget tree outside of any [View] widget, as a child
/// to a another [ViewCollection], or in the [ViewAnchor.view] slot of a
/// [ViewAnchor] widget. It is not required to be a direct child of those
/// widgets; other non-[RenderObjectWidget]s may appear in between the two (such
/// as an [InheritedWidget]).
///
/// Similarly, the [views] children of this widget must be [View]s, but they
/// may be wrapped in additional non-[RenderObjectWidget]s (e.g.
/// [InheritedWidget]s).
///
/// See also:
///
/// * [WidgetsBinding] for an explanation of rendering and non-rendering zones.
class ViewCollection extends _MultiChildComponentWidget {
/// Creates a [ViewCollection] widget.
const ViewCollection({super.key, required super.views});
/// The [View] descendants of this widget.
///
/// The [View]s may be wrapped in other non-[RenderObjectWidget]s (e.g.
/// [InheritedWidget]s). However, no [RenderObjectWidget] is allowed to appear
/// between the [ViewCollection] and the next [View] widget.
List<Widget> get views => _views;
}
/// Decorates a [child] widget with a side [View].
///
/// This widget must have a [View] ancestor, into which the [child] widget
/// is rendered.
///
/// Typically, a [View] or [ViewCollection] widget is used in the [view] slot to
/// define the content of the side view(s). Those widgets may be wrapped in
/// other non-[RenderObjectWidget]s (e.g. [InheritedWidget]s). However, no
/// [RenderObjectWidget] is allowed to appear between the [ViewAnchor] and the
/// next [View] widget in the [view] slot. The widgets in the [view] slot have
/// access to all [InheritedWidget]s above the [ViewAnchor] in the tree.
///
/// In technical terms, the [ViewAnchor] can only be used in a rendering zone of
/// the widget tree and the [view] slot marks the start of a new non-rendering
/// zone (see [WidgetsBinding] for a definition of these zones). Typically,
/// it is occupied by a [View] widget, which will start a new rendering zone.
///
/// {@template flutter.widgets.ViewAnchor}
/// An example use case for this widget is a tooltip for a button. The tooltip
/// should be able to extend beyond the bounds of the main view. For this, the
/// tooltip can be implemented as a separate [View], which is anchored to the
/// button in the main view by wrapping that button with a [ViewAnchor]. In this
/// example, the [view] slot is configured with the tooltip [View] and the
/// [child] is the button widget rendered into the surrounding view.
/// {@endtemplate}
class ViewAnchor extends StatelessWidget {
/// Creates a [ViewAnchor] widget.
const ViewAnchor({
super.key,
this.view,
required this.child,
});
/// The widget that defines the view anchored to this widget.
///
/// Typically, a [View] or [ViewCollection] widget is used, which may be
/// wrapped in other non-[RenderObjectWidget]s (e.g. [InheritedWidget]s).
///
/// {@macro flutter.widgets.ViewAnchor}
final Widget? view;
/// The widget below this widget in the tree.
///
/// It is rendered into the surrounding view, not in the view defined by
/// [view].
///
/// {@macro flutter.widgets.ViewAnchor}
final Widget child;
@override
Widget build(BuildContext context) {
return _MultiChildComponentWidget(
views: <Widget>[
if (view != null)
_ViewScope(
view: null,
child: view!,
),
],
child: child,
);
}
}
class _MultiChildComponentElement extends Element {
_MultiChildComponentElement(super.widget);
List<Element> _viewElements = <Element>[];
final Set<Element> _forgottenViewElements = HashSet<Element>();
Element? _childElement;
bool _debugAssertChildren() {
final _MultiChildComponentWidget typedWidget = widget as _MultiChildComponentWidget;
// Each view widget must have a corresponding element.
assert(_viewElements.length == typedWidget._views.length);
// Iff there is a child widget, it must have a corresponding element.
assert((_childElement == null) == (typedWidget._child == null));
// The child element is not also a view element.
assert(!_viewElements.contains(_childElement));
return true;
}
@override
void attachRenderObject(Object? newSlot) {
super.attachRenderObject(newSlot);
assert(_debugCheckMustAttachRenderObject(newSlot));
}
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
assert(_debugCheckMustAttachRenderObject(newSlot));
assert(_viewElements.isEmpty);
assert(_childElement == null);
rebuild();
assert(_debugAssertChildren());
}
@override
void updateSlot(Object? newSlot) {
super.updateSlot(newSlot);
assert(_debugCheckMustAttachRenderObject(newSlot));
}
bool _debugCheckMustAttachRenderObject(Object? slot) {
// Check only applies in the ViewCollection configuration.
if (!kDebugMode || (widget as _MultiChildComponentWidget)._child != null) {
return true;
}
bool hasAncestorRenderObjectElement = false;
bool ancestorWantsRenderObject = true;
visitAncestorElements((Element ancestor) {
if (!ancestor.debugExpectsRenderObjectForSlot(slot)) {
ancestorWantsRenderObject = false;
return false;
}
if (ancestor is RenderObjectElement) {
hasAncestorRenderObjectElement = true;
return false;
}
return true;
});
if (hasAncestorRenderObjectElement && ancestorWantsRenderObject) {
FlutterError.reportError(
FlutterErrorDetails(exception: FlutterError.fromParts(
<DiagnosticsNode>[
ErrorSummary(
'The Element for ${toStringShort()} cannot be inserted into slot "$slot" of its ancestor. ',
),
ErrorDescription(
'The ownership chain for the Element in question was:\n ${debugGetCreatorChain(10)}',
),
ErrorDescription(
'This Element allows the creation of multiple independent render trees, which cannot '
'be attached to an ancestor in an existing render tree. However, an ancestor RenderObject '
'is expecting that a child will be attached.'
),
ErrorHint(
'Try moving the subtree that contains the ${toStringShort()} widget into the '
'view property of a ViewAnchor widget or to the root of the widget tree, where '
'it is not expected to attach its RenderObject to its ancestor.',
),
],
)),
);
}
return true;
}
@override
void update(_MultiChildComponentWidget newWidget) {
// Cannot switch from ViewAnchor config to ViewCollection config.
assert((newWidget._child == null) == ((widget as _MultiChildComponentWidget)._child == null));
super.update(newWidget);
rebuild(force: true);
assert(_debugAssertChildren());
}
static const Object _viewSlot = Object();
@override
bool debugExpectsRenderObjectForSlot(Object? slot) => slot != _viewSlot;
@override
void performRebuild() {
final _MultiChildComponentWidget typedWidget = widget as _MultiChildComponentWidget;
_childElement = updateChild(_childElement, typedWidget._child, slot);
final List<Widget> views = typedWidget._views;
_viewElements = updateChildren(
_viewElements,
views,
forgottenChildren: _forgottenViewElements,
slots: List<Object>.generate(views.length, (_) => _viewSlot),
);
_forgottenViewElements.clear();
super.performRebuild(); // clears the dirty flag
assert(_debugAssertChildren());
}
@override
void forgetChild(Element child) {
if (child == _childElement) {
_childElement = null;
} else {
assert(_viewElements.contains(child));
assert(!_forgottenViewElements.contains(child));
_forgottenViewElements.add(child);
}
super.forgetChild(child);
}
@override
void visitChildren(ElementVisitor visitor) {
if (_childElement != null) {
visitor(_childElement!);
}
for (final Element child in _viewElements) {
if (!_forgottenViewElements.contains(child)) {
visitor(child);
}
}
}
@override
bool get debugDoingBuild => false; // This element does not have a concept of "building".
@override
Element? get renderObjectAttachingChild => _childElement;
@override
List<DiagnosticsNode> debugDescribeChildren() {
return <DiagnosticsNode>[
if (_childElement != null) _childElement!.toDiagnosticsNode(),
for (int i = 0; i < _viewElements.length; i++)
_viewElements[i].toDiagnosticsNode(
name: 'view ${i + 1}',
style: DiagnosticsTreeStyle.offstage,
),
];
}
}
// A special [GlobalKey] to support passing the deprecated
// [RendererBinding.renderView] and [RendererBinding.pipelineOwner] to the
// [_RawView]. Will be removed when those deprecated properties are removed.
@optionalTypeArgs
class _DeprecatedRawViewKey<T extends State<StatefulWidget>> extends GlobalKey<T> {
const _DeprecatedRawViewKey(this.view, this.owner, this.renderView) : super.constructor();
final FlutterView view;
final PipelineOwner? owner;
final RenderView? renderView;
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is _DeprecatedRawViewKey<T>
&& identical(other.view, view)
&& identical(other.owner, owner)
&& identical(other.renderView, renderView);
}
@override
int get hashCode => Object.hash(view, owner, renderView);
@override
String toString() => '[_DeprecatedRawViewKey ${describeIdentity(view)}]';
}