mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Add documentation examples to KeepAlive
AutomaticKeepAlive
and AutomaticKeepAliveClientMixin
(#168137)
Part of https://github.com/flutter/flutter/issues/153860 https://github.com/user-attachments/assets/8fd233da-d1b1-417a-8854-8d16c3ad195b https://github.com/user-attachments/assets/93197949-b962-483e-bb8f-02db5206674c https://github.com/user-attachments/assets/ad4f4091-6ed3-4a13-bcf3-f8572ce87481 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
parent
c795935606
commit
c6ceffa2e7
@ -0,0 +1,69 @@
|
||||
// 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 'package:flutter/material.dart';
|
||||
|
||||
/// Flutter code sample for [AutomaticKeepAlive].
|
||||
///
|
||||
/// This example demonstrates how to use the [AutomaticKeepAlive] to preserve the state
|
||||
/// of individual list items in a `ListView` when they are scrolled out of view.
|
||||
/// Each item has a counter that maintains its state.
|
||||
void main() {
|
||||
runApp(const AutomaticKeepAliveExampleApp());
|
||||
}
|
||||
|
||||
class AutomaticKeepAliveExampleApp extends StatelessWidget {
|
||||
const AutomaticKeepAliveExampleApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
appBar: AppBar(title: const Text('AutomaticKeepAlive Example')),
|
||||
body: ListView.builder(
|
||||
addAutomaticKeepAlives: false,
|
||||
addRepaintBoundaries: false,
|
||||
addSemanticIndexes: false,
|
||||
itemCount: 100,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return AutomaticKeepAlive(child: _KeepAliveItem(index: index));
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _KeepAliveItem extends StatefulWidget {
|
||||
const _KeepAliveItem({required this.index});
|
||||
|
||||
final int index;
|
||||
|
||||
@override
|
||||
State<_KeepAliveItem> createState() => _KeepAliveItemState();
|
||||
}
|
||||
|
||||
class _KeepAliveItemState extends State<_KeepAliveItem>
|
||||
with AutomaticKeepAliveClientMixin<_KeepAliveItem> {
|
||||
int _counter = 0;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => widget.index.isEven;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return ListTile(
|
||||
title: Text('Item ${widget.index}: $_counter'),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_counter++;
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
// 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 'package:flutter/material.dart';
|
||||
|
||||
/// Flutter code sample for [AutomaticKeepAliveClientMixin].
|
||||
///
|
||||
/// This example demonstrates how to use the [AutomaticKeepAliveClientMixin] to
|
||||
/// preserve the state of individual list items in a `ListView` when they are
|
||||
/// scrolled out of view. Each item has a counter that maintains its state.
|
||||
void main() {
|
||||
runApp(const AutomaticKeepAliveClientMixinExampleApp());
|
||||
}
|
||||
|
||||
class AutomaticKeepAliveClientMixinExampleApp extends StatefulWidget {
|
||||
const AutomaticKeepAliveClientMixinExampleApp({super.key});
|
||||
|
||||
@override
|
||||
State<AutomaticKeepAliveClientMixinExampleApp> createState() =>
|
||||
_AutomaticKeepAliveClientMixinExampleAppState();
|
||||
}
|
||||
|
||||
class _AutomaticKeepAliveClientMixinExampleAppState
|
||||
extends State<AutomaticKeepAliveClientMixinExampleApp> {
|
||||
bool _keepAlive = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('AutomaticKeepAliveClientMixin Example'),
|
||||
actions: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
const Text('Keep Alive'),
|
||||
Switch(
|
||||
value: _keepAlive,
|
||||
onChanged: (bool value) {
|
||||
setState(() {
|
||||
_keepAlive = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
body: ListView.builder(
|
||||
itemCount: 100,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return _KeepAliveItem(index: index, keepAlive: _keepAlive);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _KeepAliveItem extends StatefulWidget {
|
||||
const _KeepAliveItem({required this.index, required this.keepAlive});
|
||||
|
||||
final int index;
|
||||
final bool keepAlive;
|
||||
|
||||
@override
|
||||
State<_KeepAliveItem> createState() => _KeepAliveItemState();
|
||||
}
|
||||
|
||||
class _KeepAliveItemState extends State<_KeepAliveItem>
|
||||
with AutomaticKeepAliveClientMixin<_KeepAliveItem> {
|
||||
int _counter = 0;
|
||||
|
||||
@override
|
||||
void didUpdateWidget(_KeepAliveItem oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.keepAlive != widget.keepAlive) {
|
||||
updateKeepAlive();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => widget.keepAlive;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
|
||||
return ListTile(
|
||||
title: Text('Item ${widget.index}: $_counter'),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_counter++;
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
64
examples/api/lib/widgets/keep_alive/keep_alive.0.dart
Normal file
64
examples/api/lib/widgets/keep_alive/keep_alive.0.dart
Normal file
@ -0,0 +1,64 @@
|
||||
// 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 'package:flutter/material.dart';
|
||||
|
||||
/// Flutter code sample for [KeepAlive].
|
||||
///
|
||||
/// This example demonstrates how to use the [KeepAlive] to preserve the state
|
||||
/// of individual list items in a `ListView` when they are scrolled out of view.
|
||||
/// Each item has a counter that maintains its state.
|
||||
void main() {
|
||||
runApp(const KeepAliveExampleApp());
|
||||
}
|
||||
|
||||
class KeepAliveExampleApp extends StatelessWidget {
|
||||
const KeepAliveExampleApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
appBar: AppBar(title: const Text('KeepAlive Example')),
|
||||
body: ListView.builder(
|
||||
addAutomaticKeepAlives: false,
|
||||
addRepaintBoundaries: false,
|
||||
addSemanticIndexes: false,
|
||||
itemCount: 100,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return KeepAlive(keepAlive: index.isEven, child: _KeepAliveItem(index: index));
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _KeepAliveItem extends StatefulWidget {
|
||||
const _KeepAliveItem({required this.index});
|
||||
|
||||
final int index;
|
||||
|
||||
@override
|
||||
State<_KeepAliveItem> createState() => _KeepAliveItemState();
|
||||
}
|
||||
|
||||
class _KeepAliveItemState extends State<_KeepAliveItem> {
|
||||
int _counter = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
title: Text('Item ${widget.index}: $_counter'),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_counter++;
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
// 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 'package:flutter/material.dart';
|
||||
import 'package:flutter_api_samples/widgets/keep_alive/automatic_keep_alive.0.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('The state is maintained for the even items', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(const AutomaticKeepAliveExampleApp());
|
||||
|
||||
expect(find.text('Item 0: 0'), findsOne);
|
||||
expect(find.text('Item 1: 0'), findsOne);
|
||||
|
||||
await tester.tap(find.widgetWithIcon(IconButton, Icons.add).first);
|
||||
await tester.tap(find.widgetWithIcon(IconButton, Icons.add).at(1));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Item 0: 1'), findsOne);
|
||||
expect(find.text('Item 1: 1'), findsOne);
|
||||
|
||||
// Scrolls all the way down to the bottom of the list.
|
||||
await tester.fling(find.byType(ListView), const Offset(0, -6000), 1000);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Item 99: 0'), findsOne);
|
||||
|
||||
// Scrolls all the way back to the top of the list.
|
||||
await tester.fling(find.byType(ListView), const Offset(0, 6000), 1000);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Item 0: 1'), findsOne, reason: 'The state of item 0 should be maintained');
|
||||
expect(
|
||||
find.text('Item 1: 0'),
|
||||
findsOne,
|
||||
reason: 'The state of item 1 should not be maintained',
|
||||
);
|
||||
});
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
// 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 'package:flutter/material.dart';
|
||||
import 'package:flutter_api_samples/widgets/keep_alive/automatic_keep_alive_client_mixin.0.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('The state is maintained when the item is scrolled out of view', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
await tester.pumpWidget(const AutomaticKeepAliveClientMixinExampleApp());
|
||||
|
||||
expect(find.text('Item 0: 0'), findsOne);
|
||||
|
||||
await tester.tap(find.widgetWithIcon(IconButton, Icons.add).first);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Item 0: 1'), findsOne);
|
||||
|
||||
// Scrolls all the way down to the bottom of the list.
|
||||
await tester.fling(find.byType(ListView), const Offset(0, -6000), 1000);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Item 99: 0'), findsOne);
|
||||
|
||||
// Scrolls all the way back to the top of the list.
|
||||
await tester.fling(find.byType(ListView), const Offset(0, 6000), 1000);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Item 0: 1'), findsOne, reason: 'The state of item 0 should be maintained');
|
||||
});
|
||||
|
||||
testWidgets('The state is not maintained when the item is scrolled out of view', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
await tester.pumpWidget(const AutomaticKeepAliveClientMixinExampleApp());
|
||||
|
||||
await tester.tap(find.byType(Switch));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Item 0: 0'), findsOne);
|
||||
|
||||
await tester.tap(find.widgetWithIcon(IconButton, Icons.add).first);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Item 0: 1'), findsOne);
|
||||
|
||||
// Scrolls all the way down to the bottom of the list.
|
||||
await tester.fling(find.byType(ListView), const Offset(0, -6000), 1000);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Item 99: 0'), findsOne);
|
||||
|
||||
// Scrolls all the way back to the top of the list.
|
||||
await tester.fling(find.byType(ListView), const Offset(0, 6000), 1000);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.text('Item 0: 0'),
|
||||
findsOne,
|
||||
reason: 'The state of item 0 should not be maintained',
|
||||
);
|
||||
});
|
||||
}
|
40
examples/api/test/widgets/keep_alive/keep_alive.0_test.dart
Normal file
40
examples/api/test/widgets/keep_alive/keep_alive.0_test.dart
Normal file
@ -0,0 +1,40 @@
|
||||
// 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 'package:flutter/material.dart';
|
||||
import 'package:flutter_api_samples/widgets/keep_alive/keep_alive.0.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('The state is maintained for the even items', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(const KeepAliveExampleApp());
|
||||
|
||||
expect(find.text('Item 0: 0'), findsOne);
|
||||
expect(find.text('Item 1: 0'), findsOne);
|
||||
|
||||
await tester.tap(find.widgetWithIcon(IconButton, Icons.add).first);
|
||||
await tester.tap(find.widgetWithIcon(IconButton, Icons.add).at(1));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Item 0: 1'), findsOne);
|
||||
expect(find.text('Item 1: 1'), findsOne);
|
||||
|
||||
// Scrolls all the way down to the bottom of the list.
|
||||
await tester.fling(find.byType(ListView), const Offset(0, -6000), 1000);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Item 99: 0'), findsOne);
|
||||
|
||||
// Scrolls all the way back to the top of the list.
|
||||
await tester.fling(find.byType(ListView), const Offset(0, 6000), 1000);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Item 0: 1'), findsOne, reason: 'The state of item 0 should be maintained');
|
||||
expect(
|
||||
find.text('Item 1: 0'),
|
||||
findsOne,
|
||||
reason: 'The state of item 1 should not be maintained',
|
||||
);
|
||||
});
|
||||
}
|
@ -26,6 +26,30 @@ import 'sliver.dart';
|
||||
/// [KeepAliveNotification.handle].
|
||||
///
|
||||
/// To send these notifications, consider using [AutomaticKeepAliveClientMixin].
|
||||
///
|
||||
/// {@tool dartpad}
|
||||
/// This sample demonstrates how to use the [AutomaticKeepAlive] widget in
|
||||
/// combination with the [AutomaticKeepAliveClientMixin] to selectively preserve
|
||||
/// the state of individual items in a scrollable list.
|
||||
///
|
||||
/// Normally, widgets in a lazily built list like [ListView.builder] are
|
||||
/// disposed of when they leave the visible area to save resources. This means
|
||||
/// that any state inside a [StatefulWidget] would be lost unless explicitly
|
||||
/// preserved.
|
||||
///
|
||||
/// In this example, each list item is a [StatefulWidget] that includes a
|
||||
/// counter and an increment button. To preserve the state of selected items
|
||||
/// (based on their index), the [AutomaticKeepAlive] widget and
|
||||
/// [AutomaticKeepAliveClientMixin] are used:
|
||||
///
|
||||
/// - The `wantKeepAlive` getter in the item’s state class returns true for
|
||||
/// even-indexed items, indicating that their state should be preserved.
|
||||
/// - For odd-indexed items, `wantKeepAlive` returns false, so their state is
|
||||
/// not preserved when scrolled out of view.
|
||||
///
|
||||
/// ** See code in examples/api/lib/widgets/keep_alive/automatic_keep_alive.0.dart **
|
||||
/// {@end-tool}
|
||||
///
|
||||
class AutomaticKeepAlive extends StatefulWidget {
|
||||
/// Creates a widget that listens to [KeepAliveNotification]s and maintains a
|
||||
/// [KeepAlive] widget appropriately.
|
||||
@ -342,6 +366,13 @@ class KeepAliveHandle extends ChangeNotifier {
|
||||
/// The type argument `T` is the type of the [StatefulWidget] subclass of the
|
||||
/// [State] into which this class is being mixed.
|
||||
///
|
||||
/// {@tool dartpad}
|
||||
/// This example demonstrates how to use the [AutomaticKeepAliveClientMixin]
|
||||
/// to keep the state of a widget alive even when it is scrolled out of view.
|
||||
///
|
||||
/// ** See code in examples/api/lib/widgets/keep_alive/automatic_keep_alive_client_mixin.0.dart **
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [AutomaticKeepAlive], which listens to messages from this mixin.
|
||||
|
@ -1464,6 +1464,30 @@ class _SliverOffstageElement extends SingleChildRenderObjectElement {
|
||||
/// In practice, the simplest way to deal with these notifications is to mix
|
||||
/// [AutomaticKeepAliveClientMixin] into one's [State]. See the documentation
|
||||
/// for that mixin class for details.
|
||||
///
|
||||
/// {@tool dartpad}
|
||||
/// This sample demonstrates how to use the [KeepAlive] widget
|
||||
/// to preserve the state of individual list items in a [ListView] when they are
|
||||
/// scrolled out of view.
|
||||
///
|
||||
/// By default, [ListView.builder] only keeps the widgets currently visible in
|
||||
/// the viewport alive. When an item scrolls out of view, it may be disposed to
|
||||
/// free up resources. This can cause the state of [StatefulWidget]s to be lost
|
||||
/// if not explicitly preserved.
|
||||
///
|
||||
/// In this example, each item in the list is a [StatefulWidget] that maintains
|
||||
/// a counter. Tapping the "+" button increments the counter. To selectively
|
||||
/// preserve the state, each item is wrapped in a [KeepAlive] widget, with the
|
||||
/// keepAlive parameter set based on the item’s index:
|
||||
///
|
||||
/// - For even-indexed items, `keepAlive: true`, so their state is preserved
|
||||
/// even when scrolled off-screen.
|
||||
/// - For odd-indexed items, `keepAlive: false`, so their state is discarded
|
||||
/// when they are no longer visible.
|
||||
///
|
||||
/// ** See code in examples/api/lib/widgets/keep_alive/keep_alive.0.dart **
|
||||
/// {@end-tool}
|
||||
///
|
||||
class KeepAlive extends ParentDataWidget<KeepAliveParentDataMixin> {
|
||||
/// Marks a child as needing to remain alive.
|
||||
const KeepAlive({super.key, required this.keepAlive, required super.child});
|
||||
|
Loading…
Reference in New Issue
Block a user