forked from firka/flutter
Fix memory leak in ListWheelScrollView (#134732)
This commit is contained in:
committed by
GitHub
parent
1e4a1be681
commit
09acfe6341
@@ -7,7 +7,6 @@ import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/physics.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
|
||||
import 'basic.dart';
|
||||
import 'framework.dart';
|
||||
@@ -709,12 +708,14 @@ class ListWheelScrollView extends StatefulWidget {
|
||||
|
||||
class _ListWheelScrollViewState extends State<ListWheelScrollView> {
|
||||
int _lastReportedItemIndex = 0;
|
||||
ScrollController? scrollController;
|
||||
ScrollController? _backupController;
|
||||
|
||||
ScrollController get _effectiveController =>
|
||||
widget.controller ?? (_backupController ??= FixedExtentScrollController());
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
scrollController = widget.controller ?? FixedExtentScrollController();
|
||||
if (widget.controller is FixedExtentScrollController) {
|
||||
final FixedExtentScrollController controller = widget.controller! as FixedExtentScrollController;
|
||||
_lastReportedItemIndex = controller.initialItem;
|
||||
@@ -722,15 +723,9 @@ class _ListWheelScrollViewState extends State<ListWheelScrollView> {
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(ListWheelScrollView oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.controller != null && widget.controller != scrollController) {
|
||||
final ScrollController? oldScrollController = scrollController;
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
oldScrollController!.dispose();
|
||||
});
|
||||
scrollController = widget.controller;
|
||||
}
|
||||
void dispose() {
|
||||
_backupController?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
bool _handleScrollNotification(ScrollNotification notification) {
|
||||
@@ -754,7 +749,7 @@ class _ListWheelScrollViewState extends State<ListWheelScrollView> {
|
||||
return NotificationListener<ScrollNotification>(
|
||||
onNotification: _handleScrollNotification,
|
||||
child: _FixedExtentScrollable(
|
||||
controller: scrollController,
|
||||
controller: _effectiveController,
|
||||
physics: widget.physics,
|
||||
itemExtent: widget.itemExtent,
|
||||
restorationId: widget.restorationId,
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
|
||||
|
||||
import '../rendering/rendering_tester.dart' show TestCallbackPainter, TestClipPaintingContext;
|
||||
|
||||
@@ -1285,7 +1286,7 @@ void main() {
|
||||
expect(controller.selectedItem, 10);
|
||||
});
|
||||
|
||||
testWidgets('controller hot swappable', (WidgetTester tester) async {
|
||||
testWidgetsWithLeakTracking('controller hot swappable', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
@@ -1302,14 +1303,16 @@ void main() {
|
||||
await tester.drag(find.byType(ListWheelScrollView), const Offset(0.0, -500.0));
|
||||
await tester.pump();
|
||||
|
||||
final FixedExtentScrollController newController =
|
||||
final FixedExtentScrollController controller1 =
|
||||
FixedExtentScrollController(initialItem: 30);
|
||||
addTearDown(controller1.dispose);
|
||||
|
||||
// Attaching first controller.
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: ListWheelScrollView(
|
||||
controller: newController,
|
||||
controller: controller1,
|
||||
itemExtent: 100.0,
|
||||
children: List<Widget>.generate(100, (int index) {
|
||||
return const Placeholder();
|
||||
@@ -1320,13 +1323,41 @@ void main() {
|
||||
|
||||
// initialItem doesn't do anything since the scroll position was already
|
||||
// created.
|
||||
expect(newController.selectedItem, 5);
|
||||
expect(controller1.selectedItem, 5);
|
||||
|
||||
newController.jumpToItem(50);
|
||||
expect(newController.selectedItem, 50);
|
||||
expect(newController.position.pixels, 5000.0);
|
||||
controller1.jumpToItem(50);
|
||||
expect(controller1.selectedItem, 50);
|
||||
expect(controller1.position.pixels, 5000.0);
|
||||
|
||||
// Now remove the controller
|
||||
final FixedExtentScrollController controller2 =
|
||||
FixedExtentScrollController(initialItem: 33);
|
||||
addTearDown(controller2.dispose);
|
||||
|
||||
// Attaching the second controller.
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: ListWheelScrollView(
|
||||
controller: controller2,
|
||||
itemExtent: 100.0,
|
||||
children: List<Widget>.generate(100, (int index) {
|
||||
return const Placeholder();
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// First controller is now detached.
|
||||
expect(controller1.hasClients, isFalse);
|
||||
// initialItem doesn't do anything since the scroll position was already
|
||||
// created.
|
||||
expect(controller2.selectedItem, 50);
|
||||
|
||||
controller2.jumpToItem(40);
|
||||
expect(controller2.selectedItem, 40);
|
||||
expect(controller2.position.pixels, 4000.0);
|
||||
|
||||
// Now, use the internal controller.
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
@@ -1339,9 +1370,59 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
// Internally, that same controller is still attached and still at the
|
||||
// same place.
|
||||
expect(newController.selectedItem, 50);
|
||||
// Both controllers are now detached.
|
||||
expect(controller1.hasClients, isFalse);
|
||||
expect(controller2.hasClients, isFalse);
|
||||
});
|
||||
|
||||
testWidgetsWithLeakTracking('controller can be reused', (WidgetTester tester) async {
|
||||
final FixedExtentScrollController controller =
|
||||
FixedExtentScrollController(initialItem: 3);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: ListWheelScrollView(
|
||||
controller: controller,
|
||||
itemExtent: 100.0,
|
||||
children: List<Widget>.generate(100, (int index) {
|
||||
return const Placeholder();
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// selectedItem is equal to the initialItem.
|
||||
expect(controller.selectedItem, 3);
|
||||
expect(controller.position.pixels, 300.0);
|
||||
|
||||
controller.jumpToItem(10);
|
||||
expect(controller.selectedItem, 10);
|
||||
expect(controller.position.pixels, 1000.0);
|
||||
|
||||
await tester.pumpWidget(const Center());
|
||||
|
||||
// Controller is now detached.
|
||||
expect(controller.hasClients, isFalse);
|
||||
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: ListWheelScrollView(
|
||||
controller: controller,
|
||||
itemExtent: 100.0,
|
||||
children: List<Widget>.generate(100, (int index) {
|
||||
return const Placeholder();
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Controller is now attached again.
|
||||
expect(controller.hasClients, isTrue);
|
||||
expect(controller.selectedItem, 3);
|
||||
expect(controller.position.pixels, 300.0);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user