mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Instrument RestorationBucket, _RouteEntry and DisposableBuildContext for leak tracking. (#137477)
This commit is contained in:
parent
50ecd5700f
commit
f05bb9a182
@ -420,6 +420,12 @@ class RestorationManager extends ChangeNotifier {
|
||||
_doSerialization();
|
||||
assert(!_serializationScheduled);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_rootBucket?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// A [RestorationBucket] holds pieces of the restoration data that a part of
|
||||
@ -507,6 +513,9 @@ class RestorationBucket {
|
||||
_debugOwner = debugOwner;
|
||||
return true;
|
||||
}());
|
||||
if (kFlutterMemoryAllocationsEnabled) {
|
||||
_maybeDispatchObjectCreation();
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the root [RestorationBucket] for the provided restoration
|
||||
@ -540,6 +549,9 @@ class RestorationBucket {
|
||||
_debugOwner = manager;
|
||||
return true;
|
||||
}());
|
||||
if (kFlutterMemoryAllocationsEnabled) {
|
||||
_maybeDispatchObjectCreation();
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a child bucket initialized with the data that the provided
|
||||
@ -563,6 +575,9 @@ class RestorationBucket {
|
||||
_debugOwner = debugOwner;
|
||||
return true;
|
||||
}());
|
||||
if (kFlutterMemoryAllocationsEnabled) {
|
||||
_maybeDispatchObjectCreation();
|
||||
}
|
||||
}
|
||||
|
||||
static const String _childrenMapKey = 'c';
|
||||
@ -934,6 +949,19 @@ class RestorationBucket {
|
||||
_parent?._addChildData(this);
|
||||
}
|
||||
|
||||
// TODO(polina-c): stop duplicating code across disposables
|
||||
// https://github.com/flutter/flutter/issues/137435
|
||||
/// Dispatches event of object creation to [MemoryAllocations.instance].
|
||||
void _maybeDispatchObjectCreation() {
|
||||
if (kFlutterMemoryAllocationsEnabled) {
|
||||
MemoryAllocations.instance.dispatchObjectCreated(
|
||||
library: 'package:flutter/services.dart',
|
||||
className: '$RestorationBucket',
|
||||
object: this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Deletes the bucket and all the data stored in it from the bucket
|
||||
/// hierarchy.
|
||||
///
|
||||
@ -948,6 +976,11 @@ class RestorationBucket {
|
||||
/// This method must only be called by the object's owner.
|
||||
void dispose() {
|
||||
assert(_debugAssertNotDisposed());
|
||||
// TODO(polina-c): stop duplicating code across disposables
|
||||
// https://github.com/flutter/flutter/issues/137435
|
||||
if (kFlutterMemoryAllocationsEnabled) {
|
||||
MemoryAllocations.instance.dispatchObjectDisposed(object: this);
|
||||
}
|
||||
_visitChildren(_dropChild, concurrentModification: true);
|
||||
_claimedChildren.clear();
|
||||
_childrenToAdd.clear();
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'framework.dart';
|
||||
|
||||
/// Provides non-leaking access to a [BuildContext].
|
||||
@ -28,7 +30,17 @@ class DisposableBuildContext<T extends State> {
|
||||
///
|
||||
/// [State.mounted] must be true.
|
||||
DisposableBuildContext(T this._state)
|
||||
: assert(_state.mounted, 'A DisposableBuildContext was given a BuildContext for an Element that is not mounted.');
|
||||
: assert(_state.mounted, 'A DisposableBuildContext was given a BuildContext for an Element that is not mounted.') {
|
||||
// TODO(polina-c): stop duplicating code across disposables
|
||||
// https://github.com/flutter/flutter/issues/137435
|
||||
if (kFlutterMemoryAllocationsEnabled) {
|
||||
MemoryAllocations.instance.dispatchObjectCreated(
|
||||
library: 'package:flutter/widgets.dart',
|
||||
className: '$DisposableBuildContext',
|
||||
object: this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
T? _state;
|
||||
|
||||
@ -66,6 +78,11 @@ class DisposableBuildContext<T extends State> {
|
||||
/// Creators of this object must call [dispose] when their [Element] is
|
||||
/// unmounted, i.e. when [State.dispose] is called.
|
||||
void dispose() {
|
||||
// TODO(polina-c): stop duplicating code across disposables
|
||||
// https://github.com/flutter/flutter/issues/137435
|
||||
if (kFlutterMemoryAllocationsEnabled) {
|
||||
MemoryAllocations.instance.dispatchObjectDisposed(object: this);
|
||||
}
|
||||
_state = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2913,7 +2913,17 @@ class _RouteEntry extends RouteTransitionRecord {
|
||||
initialState == _RouteLifecycle.pushReplace ||
|
||||
initialState == _RouteLifecycle.replace,
|
||||
),
|
||||
currentState = initialState;
|
||||
currentState = initialState {
|
||||
// TODO(polina-c): stop duplicating code across disposables
|
||||
// https://github.com/flutter/flutter/issues/137435
|
||||
if (kFlutterMemoryAllocationsEnabled) {
|
||||
MemoryAllocations.instance.dispatchObjectCreated(
|
||||
library: 'package:flutter/widgets.dart',
|
||||
className: '$_RouteEntry',
|
||||
object: this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
final Route<dynamic> route;
|
||||
@ -3125,6 +3135,11 @@ class _RouteEntry extends RouteTransitionRecord {
|
||||
/// before disposing.
|
||||
void forcedDispose() {
|
||||
assert(currentState.index < _RouteLifecycle.disposed.index);
|
||||
// TODO(polina-c): stop duplicating code across disposables
|
||||
// https://github.com/flutter/flutter/issues/137435
|
||||
if (kFlutterMemoryAllocationsEnabled) {
|
||||
MemoryAllocations.instance.dispatchObjectDisposed(object: this);
|
||||
}
|
||||
currentState = _RouteLifecycle.disposed;
|
||||
route.dispose();
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
|
||||
|
||||
import 'restoration.dart';
|
||||
|
||||
@ -562,6 +563,51 @@ void main() {
|
||||
expect(() => bucket.rename('bar'), throwsFlutterError);
|
||||
expect(() => bucket.dispose(), throwsFlutterError);
|
||||
});
|
||||
|
||||
test('$RestorationBucket dispatches memory events', () async {
|
||||
await expectLater(
|
||||
await memoryEvents(
|
||||
() => RestorationBucket.empty(
|
||||
restorationId: 'child1',
|
||||
debugOwner: null,
|
||||
).dispose(),
|
||||
RestorationBucket,
|
||||
),
|
||||
areCreateAndDispose,
|
||||
);
|
||||
|
||||
final MockRestorationManager manager1 = MockRestorationManager();
|
||||
addTearDown(manager1.dispose);
|
||||
await expectLater(
|
||||
await memoryEvents(
|
||||
() => RestorationBucket.root(
|
||||
manager: manager1,
|
||||
rawData: null,
|
||||
).dispose(),
|
||||
RestorationBucket,
|
||||
),
|
||||
areCreateAndDispose,
|
||||
);
|
||||
|
||||
final MockRestorationManager manager2 = MockRestorationManager();
|
||||
addTearDown(manager2.dispose);
|
||||
final RestorationBucket parent = RestorationBucket.root(
|
||||
manager: manager2,
|
||||
rawData: _createRawDataSet()
|
||||
);
|
||||
addTearDown(parent.dispose);
|
||||
await expectLater(
|
||||
await memoryEvents(
|
||||
() => RestorationBucket.child(
|
||||
restorationId: 'child1',
|
||||
parent: parent,
|
||||
debugOwner: null,
|
||||
).dispose(),
|
||||
RestorationBucket,
|
||||
),
|
||||
areCreateAndDispose,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Map<String, dynamic> _createRawDataSet() {
|
||||
|
||||
@ -57,6 +57,7 @@ void main() {
|
||||
expect(rootBucket!.read<int>('value1'), 10);
|
||||
expect(rootBucket!.read<String>('value2'), 'Hello');
|
||||
final RestorationBucket child = rootBucket!.claimChild('child1', debugOwner: null);
|
||||
addTearDown(child.dispose);
|
||||
expect(child.read<int>('another value'), 22);
|
||||
|
||||
// Accessing the root bucket again completes synchronously with same bucket.
|
||||
@ -157,6 +158,7 @@ void main() {
|
||||
expect(newRoot!.read<int>('foo'), 33);
|
||||
expect(newRoot!.read<int>('value1'), null);
|
||||
final RestorationBucket newChild = newRoot!.claimChild('childFoo', debugOwner: null);
|
||||
addTearDown(newChild.dispose);
|
||||
expect(newChild.read<String>('bar'), 'Hello');
|
||||
});
|
||||
|
||||
|
||||
@ -30,6 +30,21 @@ void main() {
|
||||
|
||||
expect(() => DisposableBuildContext(state), throwsAssertionError);
|
||||
});
|
||||
|
||||
testWidgetsWithLeakTracking('DisposableBuildContext dispatches memory events', (WidgetTester tester) async {
|
||||
final GlobalKey<TestWidgetState> key = GlobalKey<TestWidgetState>();
|
||||
await tester.pumpWidget(TestWidget(key));
|
||||
|
||||
final TestWidgetState state = key.currentState!;
|
||||
|
||||
await expectLater(
|
||||
await memoryEvents(
|
||||
() => DisposableBuildContext<TestWidgetState>(state).dispose(),
|
||||
DisposableBuildContext<TestWidgetState>,
|
||||
),
|
||||
areCreateAndDispose,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
class TestWidget extends StatefulWidget {
|
||||
|
||||
@ -15,6 +15,7 @@ void main() {
|
||||
addTearDown(manager.dispose);
|
||||
final Map<String, dynamic> rawData = <String, dynamic>{};
|
||||
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
|
||||
addTearDown(root.dispose);
|
||||
expect(rawData, isEmpty);
|
||||
|
||||
await tester.pumpWidget(
|
||||
@ -41,6 +42,7 @@ void main() {
|
||||
final MockRestorationManager manager = MockRestorationManager();
|
||||
addTearDown(manager.dispose);
|
||||
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: _createRawDataSet());
|
||||
addTearDown(root.dispose);
|
||||
|
||||
await tester.pumpWidget(
|
||||
UnmanagedRestorationScope(
|
||||
@ -64,6 +66,7 @@ void main() {
|
||||
final MockRestorationManager manager = MockRestorationManager();
|
||||
addTearDown(manager.dispose);
|
||||
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: _createRawDataSet());
|
||||
addTearDown(root.dispose);
|
||||
|
||||
await tester.pumpWidget(
|
||||
UnmanagedRestorationScope(
|
||||
@ -107,6 +110,7 @@ void main() {
|
||||
final MockRestorationManager manager = MockRestorationManager();
|
||||
addTearDown(manager.dispose);
|
||||
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: _createRawDataSet());
|
||||
addTearDown(root.dispose);
|
||||
|
||||
await tester.pumpWidget(
|
||||
UnmanagedRestorationScope(
|
||||
@ -144,6 +148,7 @@ void main() {
|
||||
addTearDown(manager.dispose);
|
||||
final Map<String, dynamic> rawData = _createRawDataSet();
|
||||
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
|
||||
addTearDown(root.dispose);
|
||||
|
||||
expect((rawData[childrenMapKey] as Map<String, dynamic>).containsKey('child1'), isTrue);
|
||||
await tester.pumpWidget(
|
||||
@ -173,6 +178,7 @@ void main() {
|
||||
addTearDown(manager.dispose);
|
||||
final Map<String, dynamic> rawData = _createRawDataSet();
|
||||
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
|
||||
addTearDown(root.dispose);
|
||||
|
||||
await tester.pumpWidget(
|
||||
UnmanagedRestorationScope(
|
||||
@ -235,6 +241,7 @@ void main() {
|
||||
addTearDown(manager.dispose);
|
||||
final Map<String, dynamic> rawData = _createRawDataSet();
|
||||
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
|
||||
addTearDown(root.dispose);
|
||||
|
||||
await tester.pumpWidget(
|
||||
_TestRestorableWidget(
|
||||
@ -297,6 +304,7 @@ void main() {
|
||||
addTearDown(manager.dispose);
|
||||
final Map<String, dynamic> rawData = <String, dynamic>{};
|
||||
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
|
||||
addTearDown(root.dispose);
|
||||
final Key key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(
|
||||
|
||||
@ -15,6 +15,7 @@ void main() {
|
||||
restorationId: 'foo',
|
||||
debugOwner: 'owner',
|
||||
);
|
||||
addTearDown(bucket1.dispose);
|
||||
|
||||
await tester.pumpWidget(
|
||||
UnmanagedRestorationScope(
|
||||
@ -31,6 +32,8 @@ void main() {
|
||||
restorationId: 'foo2',
|
||||
debugOwner: 'owner',
|
||||
);
|
||||
addTearDown(bucket2.dispose);
|
||||
|
||||
await tester.pumpWidget(
|
||||
UnmanagedRestorationScope(
|
||||
bucket: bucket2,
|
||||
@ -104,6 +107,7 @@ void main() {
|
||||
addTearDown(manager.dispose);
|
||||
final Map<String, dynamic> rawData = <String, dynamic>{};
|
||||
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
|
||||
addTearDown(root.dispose);
|
||||
expect(rawData, isEmpty);
|
||||
|
||||
await tester.pumpWidget(
|
||||
@ -126,6 +130,7 @@ void main() {
|
||||
final MockRestorationManager manager = MockRestorationManager();
|
||||
addTearDown(manager.dispose);
|
||||
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: _createRawDataSet());
|
||||
addTearDown(root.dispose);
|
||||
|
||||
await tester.pumpWidget(
|
||||
UnmanagedRestorationScope(
|
||||
@ -147,6 +152,7 @@ void main() {
|
||||
final MockRestorationManager manager = MockRestorationManager();
|
||||
addTearDown(manager.dispose);
|
||||
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: _createRawDataSet());
|
||||
addTearDown(root.dispose);
|
||||
|
||||
await tester.pumpWidget(
|
||||
UnmanagedRestorationScope(
|
||||
@ -187,6 +193,7 @@ void main() {
|
||||
addTearDown(manager.dispose);
|
||||
final Map<String, dynamic> rawData = _createRawDataSet();
|
||||
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
|
||||
addTearDown(root.dispose);
|
||||
|
||||
expect((rawData[childrenMapKey] as Map<String, dynamic>).containsKey('child1'), isTrue);
|
||||
await tester.pumpWidget(
|
||||
@ -216,6 +223,7 @@ void main() {
|
||||
final MockRestorationManager manager = MockRestorationManager();
|
||||
addTearDown(manager.dispose);
|
||||
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: <String, dynamic>{});
|
||||
addTearDown(root.dispose);
|
||||
|
||||
await tester.pumpWidget(
|
||||
UnmanagedRestorationScope(
|
||||
@ -274,6 +282,8 @@ void main() {
|
||||
final MockRestorationManager manager = MockRestorationManager();
|
||||
addTearDown(manager.dispose);
|
||||
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: <String, dynamic>{});
|
||||
addTearDown(root.dispose);
|
||||
|
||||
await tester.pumpWidget(
|
||||
UnmanagedRestorationScope(
|
||||
bucket: root,
|
||||
@ -316,6 +326,7 @@ void main() {
|
||||
addTearDown(manager.dispose);
|
||||
final Map<String, dynamic> rawData = <String, dynamic>{};
|
||||
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
|
||||
addTearDown(root.dispose);
|
||||
final Key scopeKey = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(
|
||||
|
||||
@ -27,6 +27,7 @@ void main() {
|
||||
addTearDown(manager.dispose);
|
||||
final Map<String, dynamic> rawData = <String, dynamic>{};
|
||||
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
|
||||
addTearDown(root.dispose);
|
||||
expect(rawData, isEmpty);
|
||||
|
||||
await tester.pumpWidget(
|
||||
@ -77,6 +78,7 @@ void main() {
|
||||
// Complete the future.
|
||||
final Map<String, dynamic> rawData = <String, dynamic>{};
|
||||
final RestorationBucket root = RestorationBucket.root(manager: binding.restorationManager, rawData: rawData);
|
||||
addTearDown(root.dispose);
|
||||
bucketCompleter.complete(root);
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
@ -92,6 +94,7 @@ void main() {
|
||||
testWidgetsWithLeakTracking('no delay when root is available synchronously', (WidgetTester tester) async {
|
||||
final Map<String, dynamic> rawData = <String, dynamic>{};
|
||||
final RestorationBucket root = RestorationBucket.root(manager: binding.restorationManager, rawData: rawData);
|
||||
addTearDown(root.dispose);
|
||||
binding.restorationManager.rootBucket = SynchronousFuture<RestorationBucket>(root);
|
||||
|
||||
await tester.pumpWidget(
|
||||
@ -156,6 +159,7 @@ void main() {
|
||||
|
||||
// Complete the future.
|
||||
final RestorationBucket root = RestorationBucket.root(manager: binding.restorationManager, rawData: <String, dynamic>{});
|
||||
addTearDown(root.dispose);
|
||||
bucketCompleter.complete(root);
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
@ -187,6 +191,7 @@ void main() {
|
||||
addTearDown(manager.dispose);
|
||||
final Map<String, dynamic> inScopeRawData = <String, dynamic>{};
|
||||
final RestorationBucket inScopeRootBucket = RestorationBucket.root(manager: manager, rawData: inScopeRawData);
|
||||
addTearDown(inScopeRootBucket.dispose);
|
||||
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
@ -231,6 +236,7 @@ void main() {
|
||||
|
||||
final Map<String, dynamic> outOfScopeRawData = <String, dynamic>{};
|
||||
final RestorationBucket outOfScopeRootBucket = RestorationBucket.root(manager: binding.restorationManager, rawData: outOfScopeRawData);
|
||||
addTearDown(outOfScopeRootBucket.dispose);
|
||||
bucketCompleter.complete(outOfScopeRootBucket);
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
@ -267,6 +273,7 @@ void main() {
|
||||
testWidgetsWithLeakTracking('injects new root when old one is decommissioned', (WidgetTester tester) async {
|
||||
final Map<String, dynamic> firstRawData = <String, dynamic>{};
|
||||
final RestorationBucket firstRoot = RestorationBucket.root(manager: binding.restorationManager, rawData: firstRawData);
|
||||
addTearDown(firstRoot.dispose);
|
||||
binding.restorationManager.rootBucket = SynchronousFuture<RestorationBucket>(firstRoot);
|
||||
|
||||
await tester.pumpWidget(
|
||||
@ -299,9 +306,9 @@ void main() {
|
||||
},
|
||||
};
|
||||
final RestorationBucket secondRoot = RestorationBucket.root(manager: binding.restorationManager, rawData: secondRawData);
|
||||
addTearDown(secondRoot.dispose);
|
||||
binding.restorationManager.rootBucket = SynchronousFuture<RestorationBucket>(secondRoot);
|
||||
await tester.pump();
|
||||
firstRoot.dispose();
|
||||
|
||||
expect(state.bucket, isNot(same(firstBucket)));
|
||||
expect(state.bucket!.read<int>('foo'), 22);
|
||||
@ -336,6 +343,7 @@ void main() {
|
||||
expect(state.bucket, isNull);
|
||||
|
||||
final RestorationBucket root = RestorationBucket.root(manager: binding.restorationManager, rawData: null);
|
||||
addTearDown(root.dispose);
|
||||
binding.restorationManager.rootBucket = SynchronousFuture<RestorationBucket>(root);
|
||||
await tester.pump();
|
||||
|
||||
@ -346,6 +354,7 @@ void main() {
|
||||
|
||||
testWidgetsWithLeakTracking('can switch to null', (WidgetTester tester) async {
|
||||
final RestorationBucket root = RestorationBucket.root(manager: binding.restorationManager, rawData: null);
|
||||
addTearDown(root.dispose);
|
||||
binding.restorationManager.rootBucket = SynchronousFuture<RestorationBucket>(root);
|
||||
|
||||
await tester.pumpWidget(
|
||||
@ -367,7 +376,6 @@ void main() {
|
||||
|
||||
binding.restorationManager.rootBucket = SynchronousFuture<RestorationBucket?>(null);
|
||||
await tester.pump();
|
||||
root.dispose();
|
||||
|
||||
expect(binding.restorationManager.rootBucketAccessed, 2);
|
||||
expect(find.text('Hello'), findsOneWidget);
|
||||
|
||||
@ -39,6 +39,7 @@ void main() {
|
||||
await tester.pumpWidget(TestWidget(key));
|
||||
|
||||
final DisposableBuildContext context = DisposableBuildContext(key.currentState!);
|
||||
addTearDown(context.dispose);
|
||||
final TestImageProvider testImageProvider = TestImageProvider(testImage.clone());
|
||||
final ScrollAwareImageProvider<TestImageProvider> imageProvider = ScrollAwareImageProvider<TestImageProvider>(
|
||||
context: context,
|
||||
@ -74,6 +75,7 @@ void main() {
|
||||
));
|
||||
|
||||
final DisposableBuildContext context = DisposableBuildContext(key.currentState!);
|
||||
addTearDown(context.dispose);
|
||||
final TestImageProvider testImageProvider = TestImageProvider(testImage.clone());
|
||||
final ScrollAwareImageProvider<TestImageProvider> imageProvider = ScrollAwareImageProvider<TestImageProvider>(
|
||||
context: context,
|
||||
@ -115,6 +117,7 @@ void main() {
|
||||
));
|
||||
|
||||
final DisposableBuildContext context = DisposableBuildContext(keys.last.currentState!);
|
||||
addTearDown(context.dispose);
|
||||
final TestImageProvider testImageProvider = TestImageProvider(testImage.clone());
|
||||
final ScrollAwareImageProvider<TestImageProvider> imageProvider = ScrollAwareImageProvider<TestImageProvider>(
|
||||
context: context,
|
||||
@ -173,6 +176,7 @@ void main() {
|
||||
));
|
||||
|
||||
final DisposableBuildContext context = DisposableBuildContext(keys.last.currentState!);
|
||||
addTearDown(context.dispose);
|
||||
final TestImageProvider testImageProvider = TestImageProvider(testImage.clone());
|
||||
final ScrollAwareImageProvider<TestImageProvider> imageProvider = ScrollAwareImageProvider<TestImageProvider>(
|
||||
context: context,
|
||||
@ -241,6 +245,7 @@ void main() {
|
||||
));
|
||||
|
||||
final DisposableBuildContext context = DisposableBuildContext(keys.last.currentState!);
|
||||
addTearDown(context.dispose);
|
||||
final TestImageProvider testImageProvider = TestImageProvider(testImage.clone());
|
||||
final ScrollAwareImageProvider<TestImageProvider> imageProvider = ScrollAwareImageProvider<TestImageProvider>(
|
||||
context: context,
|
||||
@ -307,6 +312,7 @@ void main() {
|
||||
));
|
||||
|
||||
final DisposableBuildContext context = DisposableBuildContext(key.currentState!);
|
||||
addTearDown(context.dispose);
|
||||
final TestImageProvider testImageProvider = TestImageProvider(testImage.clone());
|
||||
final ScrollAwareImageProvider<TestImageProvider> imageProvider = ScrollAwareImageProvider<TestImageProvider>(
|
||||
context: context,
|
||||
@ -359,6 +365,7 @@ void main() {
|
||||
));
|
||||
|
||||
final DisposableBuildContext context = DisposableBuildContext(key.currentState!);
|
||||
addTearDown(context.dispose);
|
||||
final TestImageProvider testImageProvider = TestImageProvider(testImage.clone());
|
||||
final ScrollAwareImageProvider<TestImageProvider> imageProvider = ScrollAwareImageProvider<TestImageProvider>(
|
||||
context: context,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user