Unverified Commit 5385132c authored by jslavitz's avatar jslavitz Committed by GitHub

Separate keep alive logic from SliverMultiBox classes (#24192)

* sliver separation and test
parent fc4f9b9e
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildSystemType</key>
<string>Original</string>
</dict>
</plist>
...@@ -121,17 +121,35 @@ abstract class RenderSliverBoxChildManager { ...@@ -121,17 +121,35 @@ abstract class RenderSliverBoxChildManager {
/// true without making any assertions. /// true without making any assertions.
bool debugAssertChildListLocked() => true; bool debugAssertChildListLocked() => true;
} }
/// Parent data structure used by [RenderSliverWithKeepAliveMixin].
/// Parent data structure used by [RenderSliverMultiBoxAdaptor]. mixin KeepAliveParentDataMixin implements ParentData {
class SliverMultiBoxAdaptorParentData extends SliverLogicalParentData with ContainerParentDataMixin<RenderBox> {
/// The index of this child according to the [RenderSliverBoxChildManager].
int index;
/// Whether to keep the child alive even when it is no longer visible. /// Whether to keep the child alive even when it is no longer visible.
bool keepAlive = false; bool keepAlive = false;
/// Whether the widget is currently being kept alive, i.e. has [keepAlive] set /// Whether the widget is currently being kept alive, i.e. has [keepAlive] set
/// to true and is offscreen. /// to true and is offscreen.
bool get keptAlive;
}
/// This class exists to dissociate [KeepAlive] from [RenderSliverMultiBoxAdaptor].
///
/// [RenderSliverWithKeepAliveMixin.setupParentData] must be implemented to use
/// a parentData class that uses the right mixin or whatever is appropriate.
mixin RenderSliverWithKeepAliveMixin implements RenderSliver {
/// Alerts the developer that the child's parentData needs to be of type
/// [KeepAliveParentDataMixin].
@override
void setupParentData(RenderObject child) {
assert(child.parentData is KeepAliveParentDataMixin);
}
}
/// Parent data structure used by [RenderSliverMultiBoxAdaptor].
class SliverMultiBoxAdaptorParentData extends SliverLogicalParentData with ContainerParentDataMixin<RenderBox>, KeepAliveParentDataMixin {
/// The index of this child according to the [RenderSliverBoxChildManager].
int index;
@override
bool get keptAlive => _keptAlive; bool get keptAlive => _keptAlive;
bool _keptAlive = false; bool _keptAlive = false;
...@@ -166,7 +184,7 @@ class SliverMultiBoxAdaptorParentData extends SliverLogicalParentData with Conta ...@@ -166,7 +184,7 @@ class SliverMultiBoxAdaptorParentData extends SliverLogicalParentData with Conta
/// * [RenderSliverGrid], which places its children in arbitrary positions. /// * [RenderSliverGrid], which places its children in arbitrary positions.
abstract class RenderSliverMultiBoxAdaptor extends RenderSliver abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
with ContainerRenderObjectMixin<RenderBox, SliverMultiBoxAdaptorParentData>, with ContainerRenderObjectMixin<RenderBox, SliverMultiBoxAdaptorParentData>,
RenderSliverHelpers { RenderSliverHelpers, RenderSliverWithKeepAliveMixin {
/// Creates a sliver with multiple box children. /// Creates a sliver with multiple box children.
/// ///
...@@ -585,4 +603,4 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver ...@@ -585,4 +603,4 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
} }
return children; return children;
} }
} }
\ No newline at end of file
...@@ -80,7 +80,7 @@ class _AutomaticKeepAliveState extends State<AutomaticKeepAlive> { ...@@ -80,7 +80,7 @@ class _AutomaticKeepAliveState extends State<AutomaticKeepAlive> {
handle.addListener(_handles[handle]); handle.addListener(_handles[handle]);
if (!_keepingAlive) { if (!_keepingAlive) {
_keepingAlive = true; _keepingAlive = true;
final ParentDataElement<SliverMultiBoxAdaptorWidget> childElement = _getChildElement(); final ParentDataElement<SliverWithKeepAliveWidget> childElement = _getChildElement();
if (childElement != null) { if (childElement != null) {
// If the child already exists, update it synchronously. // If the child already exists, update it synchronously.
_updateParentDataOfChild(childElement); _updateParentDataOfChild(childElement);
...@@ -92,7 +92,7 @@ class _AutomaticKeepAliveState extends State<AutomaticKeepAlive> { ...@@ -92,7 +92,7 @@ class _AutomaticKeepAliveState extends State<AutomaticKeepAlive> {
if (!mounted) { if (!mounted) {
return; return;
} }
final ParentDataElement<SliverMultiBoxAdaptorWidget> childElement = _getChildElement(); final ParentDataElement<SliverWithKeepAliveWidget> childElement = _getChildElement();
assert(childElement != null); assert(childElement != null);
_updateParentDataOfChild(childElement); _updateParentDataOfChild(childElement);
}); });
...@@ -105,7 +105,7 @@ class _AutomaticKeepAliveState extends State<AutomaticKeepAlive> { ...@@ -105,7 +105,7 @@ class _AutomaticKeepAliveState extends State<AutomaticKeepAlive> {
/// ///
/// While this widget is guaranteed to have a child, this may return null if /// While this widget is guaranteed to have a child, this may return null if
/// the first build of that child has not completed yet. /// the first build of that child has not completed yet.
ParentDataElement<SliverMultiBoxAdaptorWidget> _getChildElement() { ParentDataElement<SliverWithKeepAliveWidget> _getChildElement() {
assert(mounted); assert(mounted);
final Element element = context; final Element element = context;
Element childElement; Element childElement;
...@@ -131,11 +131,11 @@ class _AutomaticKeepAliveState extends State<AutomaticKeepAlive> { ...@@ -131,11 +131,11 @@ class _AutomaticKeepAliveState extends State<AutomaticKeepAlive> {
element.visitChildren((Element child) { element.visitChildren((Element child) {
childElement = child; childElement = child;
}); });
assert(childElement == null || childElement is ParentDataElement<SliverMultiBoxAdaptorWidget>); assert(childElement == null || childElement is ParentDataElement<SliverWithKeepAliveWidget>);
return childElement; return childElement;
} }
void _updateParentDataOfChild(ParentDataElement<SliverMultiBoxAdaptorWidget> childElement) { void _updateParentDataOfChild(ParentDataElement<SliverWithKeepAliveWidget> childElement) {
childElement.applyWidgetOutOfTurn(build(context)); childElement.applyWidgetOutOfTurn(build(context));
} }
...@@ -396,4 +396,4 @@ mixin AutomaticKeepAliveClientMixin<T extends StatefulWidget> on State<T> { ...@@ -396,4 +396,4 @@ mixin AutomaticKeepAliveClientMixin<T extends StatefulWidget> on State<T> {
_ensureKeepAlive(); _ensureKeepAlive();
return null; return null;
} }
} }
\ No newline at end of file
...@@ -570,10 +570,21 @@ class SliverChildListDelegate extends SliverChildDelegate { ...@@ -570,10 +570,21 @@ class SliverChildListDelegate extends SliverChildDelegate {
} }
} }
/// A base class for sliver that have [KeepAlive] children.
abstract class SliverWithKeepAliveWidget extends RenderObjectWidget {
/// Initializes fields for subclasses.
const SliverWithKeepAliveWidget({
Key key,
}) : super(key : key);
@override
RenderSliverWithKeepAliveMixin createRenderObject(BuildContext context);
}
/// A base class for sliver that have multiple box children. /// A base class for sliver that have multiple box children.
/// ///
/// Helps subclasses build their children lazily using a [SliverChildDelegate]. /// Helps subclasses build their children lazily using a [SliverChildDelegate].
abstract class SliverMultiBoxAdaptorWidget extends RenderObjectWidget { abstract class SliverMultiBoxAdaptorWidget extends SliverWithKeepAliveWidget {
/// Initializes fields for subclasses. /// Initializes fields for subclasses.
const SliverMultiBoxAdaptorWidget({ const SliverMultiBoxAdaptorWidget({
Key key, Key key,
...@@ -1213,7 +1224,7 @@ class SliverFillRemaining extends SingleChildRenderObjectWidget { ...@@ -1213,7 +1224,7 @@ class SliverFillRemaining extends SingleChildRenderObjectWidget {
/// Mark a child as needing to stay alive even when it's in a lazy list that /// Mark a child as needing to stay alive even when it's in a lazy list that
/// would otherwise remove it. /// would otherwise remove it.
/// ///
/// This widget is for use in [SliverMultiBoxAdaptorWidget]s, such as /// This widget is for use in [SliverWithKeepAliveWidget]s, such as
/// [SliverGrid] or [SliverList]. /// [SliverGrid] or [SliverList].
/// ///
/// This widget is rarely used directly. The [SliverChildBuilderDelegate] and /// This widget is rarely used directly. The [SliverChildBuilderDelegate] and
...@@ -1230,7 +1241,7 @@ class SliverFillRemaining extends SingleChildRenderObjectWidget { ...@@ -1230,7 +1241,7 @@ class SliverFillRemaining extends SingleChildRenderObjectWidget {
/// In practice, the simplest way to deal with these notifications is to mix /// In practice, the simplest way to deal with these notifications is to mix
/// [AutomaticKeepAliveClientMixin] into one's [State]. See the documentation /// [AutomaticKeepAliveClientMixin] into one's [State]. See the documentation
/// for that mixin class for details. /// for that mixin class for details.
class KeepAlive extends ParentDataWidget<SliverMultiBoxAdaptorWidget> { class KeepAlive extends ParentDataWidget<SliverWithKeepAliveWidget> {
/// Marks a child as needing to remain alive. /// Marks a child as needing to remain alive.
/// ///
/// The [child] and [keepAlive] arguments must not be null. /// The [child] and [keepAlive] arguments must not be null.
...@@ -1249,8 +1260,8 @@ class KeepAlive extends ParentDataWidget<SliverMultiBoxAdaptorWidget> { ...@@ -1249,8 +1260,8 @@ class KeepAlive extends ParentDataWidget<SliverMultiBoxAdaptorWidget> {
@override @override
void applyParentData(RenderObject renderObject) { void applyParentData(RenderObject renderObject) {
assert(renderObject.parentData is SliverMultiBoxAdaptorParentData); assert(renderObject.parentData is KeepAliveParentDataMixin);
final SliverMultiBoxAdaptorParentData parentData = renderObject.parentData; final KeepAliveParentDataMixin parentData = renderObject.parentData;
if (parentData.keepAlive != keepAlive) { if (parentData.keepAlive != keepAlive) {
parentData.keepAlive = keepAlive; parentData.keepAlive = keepAlive;
final AbstractNode targetParent = renderObject.parent; final AbstractNode targetParent = renderObject.parent;
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
...@@ -543,6 +544,17 @@ void main() { ...@@ -543,6 +544,17 @@ void main() {
expect(find.text('FooBar 3'), findsNothing); expect(find.text('FooBar 3'), findsNothing);
expect(find.text('FooBar 73'), findsOneWidget); expect(find.text('FooBar 73'), findsOneWidget);
}); });
testWidgets('AutomaticKeepAlive with SliverKeepAliveWidget', (WidgetTester tester) async {
// We're just doing a basic test here to make sure that the functionality of
// RenderSliverWithKeepAliveMixin doesn't get regressed or deleted. As testing
// the full functionality would be cumbersome.
final RenderSliverMultiBoxAdaptorAlt alternate = RenderSliverMultiBoxAdaptorAlt();
final RenderBox child = RenderBoxKeepAlive();
alternate.insert(child);
expect(alternate.children.length, 1);
});
} }
class _AlwaysKeepAlive extends StatefulWidget { class _AlwaysKeepAlive extends StatefulWidget {
...@@ -565,3 +577,57 @@ class _AlwaysKeepAliveState extends State<_AlwaysKeepAlive> with AutomaticKeepAl ...@@ -565,3 +577,57 @@ class _AlwaysKeepAliveState extends State<_AlwaysKeepAlive> with AutomaticKeepAl
); );
} }
} }
class RenderBoxKeepAlive extends RenderBox {
State<StatefulWidget> createState() => AlwaysKeepAliveRenderBoxState();
}
class AlwaysKeepAliveRenderBoxState extends State<_AlwaysKeepAlive> with AutomaticKeepAliveClientMixin<_AlwaysKeepAlive> {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
return Container(
height: 48.0,
child: const Text('keep me alive'),
);
}
}
abstract class KeepAliveParentDataMixinAlt implements KeepAliveParentDataMixin {
@override
bool keptAlive = false;
@override
bool keepAlive = false;
}
class RenderSliverMultiBoxAdaptorAlt extends RenderSliver with
KeepAliveParentDataMixinAlt,
RenderSliverHelpers,
RenderSliverWithKeepAliveMixin {
RenderSliverMultiBoxAdaptorAlt({
RenderSliverBoxChildManager childManager
}) : _childManager = childManager;
@protected
RenderSliverBoxChildManager get childManager => _childManager;
final RenderSliverBoxChildManager _childManager;
final List<RenderBox> children = <RenderBox>[];
void insert(RenderBox child, { RenderBox after }) {
children.add(child);
}
@override
void visitChildren(RenderObjectVisitor visitor) {
children.forEach(visitor);
}
@override
void performLayout() {}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment