automatic_keep_alive_test.dart 29 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'package:flutter/gestures.dart' show DragStartBehavior;
6
import 'package:flutter/rendering.dart';
7 8 9 10
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';

class Leaf extends StatefulWidget {
11
  const Leaf({ required Key key, required this.child }) : super(key: key);
12 13
  final Widget child;
  @override
14
  State<Leaf> createState() => _LeafState();
15 16 17 18
}

class _LeafState extends State<Leaf> {
  bool _keepAlive = false;
19
  KeepAliveHandle? _handle;
20 21 22 23 24 25 26 27 28 29 30 31

  @override
  void deactivate() {
    _handle?.release();
    _handle = null;
    super.deactivate();
  }

  void setKeepAlive(bool value) {
    _keepAlive = value;
    if (_keepAlive) {
      if (_handle == null) {
32
        _handle = KeepAliveHandle();
33
        KeepAliveNotification(_handle!).dispatch(context);
34 35 36 37 38 39 40 41 42 43
      }
    } else {
      _handle?.release();
      _handle = null;
    }
  }

  @override
  Widget build(BuildContext context) {
    if (_keepAlive && _handle == null) {
44
      _handle = KeepAliveHandle();
45
      KeepAliveNotification(_handle!).dispatch(context);
46 47 48 49 50
    }
    return widget.child;
  }
}

51
List<Widget> generateList(Widget child, { required bool impliedMode }) {
52
  return List<Widget>.generate(
53 54
    100,
    (int index) {
55 56
      final Widget result = Leaf(
        key: GlobalObjectKey<_LeafState>(index),
57 58
        child: child,
      );
59
      if (impliedMode) {
60
        return result;
61
      }
62
      return AutomaticKeepAlive(child: result);
63 64 65 66 67
    },
    growable: false,
  );
}

68
void tests({ required bool impliedMode }) {
69
  testWidgets('AutomaticKeepAlive with ListView with itemExtent', (WidgetTester tester) async {
70
    await tester.pumpWidget(
71
      Directionality(
72
        textDirection: TextDirection.ltr,
73
        child: ListView(
74 75
          addAutomaticKeepAlives: impliedMode,
          addRepaintBoundaries: impliedMode,
76
          addSemanticIndexes: false,
77
          itemExtent: 12.3, // about 50 widgets visible
78
          cacheExtent: 0.0,
79 80 81 82
          children: generateList(const Placeholder(), impliedMode: impliedMode),
        ),
      ),
    );
83 84
    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
85 86 87 88
    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
89 90
    await tester.drag(find.byType(ListView), const Offset(0.0, -300.0)); // about 25 widgets' worth
    await tester.pump();
91
    expect(find.byKey(const GlobalObjectKey<_LeafState>(3), skipOffstage: false), findsNothing);
92 93 94 95
    expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsOneWidget);
96
    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
97
    const GlobalObjectKey<_LeafState>(60).currentState!.setKeepAlive(true);
98 99 100 101
    await tester.drag(find.byType(ListView), const Offset(0.0, 300.0)); // back to top
    await tester.pump();
    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
102 103 104 105 106
    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
107
    const GlobalObjectKey<_LeafState>(60).currentState!.setKeepAlive(false);
108 109 110
    await tester.pump();
    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
111 112 113 114
    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
115 116 117
  });

  testWidgets('AutomaticKeepAlive with ListView without itemExtent', (WidgetTester tester) async {
118
    await tester.pumpWidget(
119
      Directionality(
120
        textDirection: TextDirection.ltr,
121
        child: ListView(
122 123
          addAutomaticKeepAlives: impliedMode,
          addRepaintBoundaries: impliedMode,
124
          addSemanticIndexes: false,
125
          cacheExtent: 0.0,
126
          children: generateList(
127
            const SizedBox(height: 12.3, child: Placeholder()), // about 50 widgets visible
128 129 130
            impliedMode: impliedMode,
          ),
        ),
131
      ),
132
    );
133 134
    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
135 136 137 138
    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
139 140
    await tester.drag(find.byType(ListView), const Offset(0.0, -300.0)); // about 25 widgets' worth
    await tester.pump();
141
    expect(find.byKey(const GlobalObjectKey<_LeafState>(3), skipOffstage: false), findsNothing);
142 143 144 145
    expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsOneWidget);
146
    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
147
    const GlobalObjectKey<_LeafState>(60).currentState!.setKeepAlive(true);
148 149 150 151
    await tester.drag(find.byType(ListView), const Offset(0.0, 300.0)); // back to top
    await tester.pump();
    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
152 153 154 155 156
    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
157
    const GlobalObjectKey<_LeafState>(60).currentState!.setKeepAlive(false);
158 159 160
    await tester.pump();
    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
161 162 163 164
    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
165 166 167
  });

  testWidgets('AutomaticKeepAlive with GridView', (WidgetTester tester) async {
168
    await tester.pumpWidget(
169
      Directionality(
170
        textDirection: TextDirection.ltr,
171
        child: GridView.count(
172 173
          addAutomaticKeepAlives: impliedMode,
          addRepaintBoundaries: impliedMode,
174
          addSemanticIndexes: false,
175 176
          crossAxisCount: 2,
          childAspectRatio: 400.0 / 24.6, // about 50 widgets visible
177
          cacheExtent: 0.0,
178
          children: generateList(
179
            const Placeholder(),
180 181 182
            impliedMode: impliedMode,
          ),
        ),
183
      ),
184
    );
185 186
    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
187 188 189 190
    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
191 192
    await tester.drag(find.byType(GridView), const Offset(0.0, -300.0)); // about 25 widgets' worth
    await tester.pump();
193
    expect(find.byKey(const GlobalObjectKey<_LeafState>(3), skipOffstage: false), findsNothing);
194 195 196 197
    expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(59)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(61)), findsOneWidget);
198
    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
199
    const GlobalObjectKey<_LeafState>(60).currentState!.setKeepAlive(true);
200 201 202 203
    await tester.drag(find.byType(GridView), const Offset(0.0, 300.0)); // back to top
    await tester.pump();
    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
204 205 206 207 208
    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(60)), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
209
    const GlobalObjectKey<_LeafState>(60).currentState!.setKeepAlive(false);
210 211 212
    await tester.pump();
    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(30)), findsOneWidget);
213 214 215 216
    expect(find.byKey(const GlobalObjectKey<_LeafState>(59), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(60), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(61), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(90), skipOffstage: false), findsNothing);
217 218 219 220 221 222 223 224
  });
}

void main() {
  group('Explicit automatic keep-alive', () { tests(impliedMode: false); });
  group('Implied automatic keep-alive', () { tests(impliedMode: true); });

  testWidgets('AutomaticKeepAlive double', (WidgetTester tester) async {
225
    await tester.pumpWidget(
226
      Directionality(
227
        textDirection: TextDirection.ltr,
228
        child: ListView(
229 230
          addAutomaticKeepAlives: false,
          addRepaintBoundaries: false,
231
          addSemanticIndexes: false,
232
          cacheExtent: 0.0,
233
          children: <Widget>[
234
            AutomaticKeepAlive(
235
              child: SizedBox(
236
                height: 400.0,
237
                child: Stack(children: const <Widget>[
238 239
                  Leaf(key: GlobalObjectKey<_LeafState>(0), child: Placeholder()),
                  Leaf(key: GlobalObjectKey<_LeafState>(1), child: Placeholder()),
240 241
                ]),
              ),
242
            ),
243 244 245
            const AutomaticKeepAlive(
              child: SizedBox(
                key: GlobalObjectKey<_LeafState>(2),
246 247
                height: 400.0,
              ),
248
            ),
249 250 251
            const AutomaticKeepAlive(
              child: SizedBox(
                key: GlobalObjectKey<_LeafState>(3),
252 253
                height: 400.0,
              ),
254
            ),
255 256
          ],
        ),
257
      ),
258
    );
259 260 261
    expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
262
    expect(find.byKey(const GlobalObjectKey<_LeafState>(3), skipOffstage: false), findsNothing);
263 264
    await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0)); // move to bottom
    await tester.pump();
265 266
    expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(1), skipOffstage: false), findsNothing);
267 268 269 270 271 272 273
    expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
    await tester.drag(find.byType(ListView), const Offset(0.0, 1000.0)); // move to top
    await tester.pump();
    expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
274
    expect(find.byKey(const GlobalObjectKey<_LeafState>(3), skipOffstage: false), findsNothing);
275
    const GlobalObjectKey<_LeafState>(0).currentState!.setKeepAlive(true);
276 277
    await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0)); // move to bottom
    await tester.pump();
278 279 280 281
    expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(1), skipOffstage: false), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsNothing);
282 283
    expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
284
    const GlobalObjectKey<_LeafState>(1).currentState!.setKeepAlive(true);
285
    await tester.pump();
286 287 288 289
    expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(1), skipOffstage: false), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsNothing);
290 291
    expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
292
    const GlobalObjectKey<_LeafState>(0).currentState!.setKeepAlive(false);
293
    await tester.pump();
294 295 296 297
    expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(1), skipOffstage: false), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsNothing);
298 299
    expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
300
    const GlobalObjectKey<_LeafState>(1).currentState!.setKeepAlive(false);
301
    await tester.pump();
302 303
    expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(1), skipOffstage: false), findsNothing);
304 305 306 307
    expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
  });

308
  testWidgets('AutomaticKeepAlive double 2', (WidgetTester tester) async {
309
    await tester.pumpWidget(
310
      Directionality(
311
        textDirection: TextDirection.ltr,
312
        child: ListView(
313 314
          addAutomaticKeepAlives: false,
          addRepaintBoundaries: false,
315
          addSemanticIndexes: false,
316
          cacheExtent: 0.0,
317
          children: <Widget>[
318
            AutomaticKeepAlive(
319
              child: SizedBox(
320
                height: 400.0,
321
                child: Stack(children: const <Widget>[
322 323
                  Leaf(key: GlobalObjectKey<_LeafState>(0), child: Placeholder()),
                  Leaf(key: GlobalObjectKey<_LeafState>(1), child: Placeholder()),
324 325
                ]),
              ),
326
            ),
327
            AutomaticKeepAlive(
328
              child: SizedBox(
329
                height: 400.0,
330
                child: Stack(children: const <Widget>[
331 332
                  Leaf(key: GlobalObjectKey<_LeafState>(2), child: Placeholder()),
                  Leaf(key: GlobalObjectKey<_LeafState>(3), child: Placeholder()),
333 334
                ]),
              ),
335
            ),
336
            AutomaticKeepAlive(
337
              child: SizedBox(
338
                height: 400.0,
339
                child: Stack(children: const <Widget>[
340 341
                  Leaf(key: GlobalObjectKey<_LeafState>(4), child: Placeholder()),
                  Leaf(key: GlobalObjectKey<_LeafState>(5), child: Placeholder()),
342 343
                ]),
              ),
344
            ),
345 346
          ],
        ),
347
      ),
348
    );
349 350 351 352
    expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
353 354
    expect(find.byKey(const GlobalObjectKey<_LeafState>(4), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(5), skipOffstage: false), findsNothing);
355
    const GlobalObjectKey<_LeafState>(0).currentState!.setKeepAlive(true);
356 357
    await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0)); // move to bottom
    await tester.pump();
358 359 360 361
    expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(1), skipOffstage: false), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsNothing);
362 363 364 365
    expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(4)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(5)), findsOneWidget);
366
    await tester.pumpWidget(Directionality(
367
      textDirection: TextDirection.ltr,
368
      child: ListView(
369 370
        addAutomaticKeepAlives: false,
        addRepaintBoundaries: false,
371
        addSemanticIndexes: false,
372
        cacheExtent: 0.0,
373
        children: <Widget>[
374
          AutomaticKeepAlive(
375
            child: SizedBox(
376
              height: 400.0,
377
              child: Stack(children: const <Widget>[
378
                Leaf(key: GlobalObjectKey<_LeafState>(1), child: Placeholder()),
379 380
              ]),
            ),
381
          ),
382
          AutomaticKeepAlive(
383
            child: SizedBox(
384
              height: 400.0,
385
              child: Stack(children: const <Widget>[
386 387
                Leaf(key: GlobalObjectKey<_LeafState>(2), child: Placeholder()),
                Leaf(key: GlobalObjectKey<_LeafState>(3), child: Placeholder()),
388 389
              ]),
            ),
390
          ),
391
          AutomaticKeepAlive(
392
            child: SizedBox(
393
              height: 400.0,
394
              child: Stack(children: const <Widget>[
395 396 397
                Leaf(key: GlobalObjectKey<_LeafState>(4), child: Placeholder()),
                Leaf(key: GlobalObjectKey<_LeafState>(5), child: Placeholder()),
                Leaf(key: GlobalObjectKey<_LeafState>(0), child: Placeholder()),
398 399
              ]),
            ),
400
          ),
401 402
        ],
      ),
403 404
    ));
    await tester.pump(); // Sometimes AutomaticKeepAlive needs an extra pump to clean things up.
405
    expect(find.byKey(const GlobalObjectKey<_LeafState>(1), skipOffstage: false), findsNothing);
406 407 408 409 410 411 412 413 414 415
    expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(4)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(5)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsOneWidget);
    await tester.drag(find.byType(ListView), const Offset(0.0, 1000.0)); // move to top
    await tester.pump();
    expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
416 417 418 419 420 421
    expect(find.byKey(const GlobalObjectKey<_LeafState>(4), skipOffstage: false), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(5), skipOffstage: false), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(4)), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(5)), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsNothing);
422
    const GlobalObjectKey<_LeafState>(0).currentState!.setKeepAlive(false);
423 424 425 426
    await tester.pump();
    expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
427 428 429
    expect(find.byKey(const GlobalObjectKey<_LeafState>(4), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(5), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsNothing);
430
    await tester.pumpWidget(Directionality(
431
      textDirection: TextDirection.ltr,
432
      child: ListView(
433 434
        addAutomaticKeepAlives: false,
        addRepaintBoundaries: false,
435
        addSemanticIndexes: false,
436
        cacheExtent: 0.0,
437
        children: <Widget>[
438
          AutomaticKeepAlive(
439
            child: SizedBox(
440
              height: 400.0,
441
              child: Stack(children: const <Widget>[
442 443
                Leaf(key: GlobalObjectKey<_LeafState>(1), child: Placeholder()),
                Leaf(key: GlobalObjectKey<_LeafState>(2), child: Placeholder()),
444 445
              ]),
            ),
446
          ),
447
          AutomaticKeepAlive(
448
            child: SizedBox(
449
              height: 400.0,
450
              child: Stack(),
451
            ),
452
          ),
453
          AutomaticKeepAlive(
454
            child: SizedBox(
455
              height: 400.0,
456
              child: Stack(children: const <Widget>[
457 458 459 460
                Leaf(key: GlobalObjectKey<_LeafState>(3), child: Placeholder()),
                Leaf(key: GlobalObjectKey<_LeafState>(4), child: Placeholder()),
                Leaf(key: GlobalObjectKey<_LeafState>(5), child: Placeholder()),
                Leaf(key: GlobalObjectKey<_LeafState>(0), child: Placeholder()),
461 462
              ]),
            ),
463
          ),
464 465
        ],
      ),
466 467 468 469
    ));
    await tester.pump(); // Sometimes AutomaticKeepAlive needs an extra pump to clean things up.
    expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(2)), findsOneWidget);
470 471 472 473
    expect(find.byKey(const GlobalObjectKey<_LeafState>(3), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(4), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(5), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(0), skipOffstage: false), findsNothing);
474
  });
475 476

  testWidgets('AutomaticKeepAlive with keepAlive set to true before initState', (WidgetTester tester) async {
477
    await tester.pumpWidget(Directionality(
478
      textDirection: TextDirection.ltr,
479
      child: ListView.builder(
480
        dragStartBehavior: DragStartBehavior.down,
481
        addSemanticIndexes: false,
482
        itemCount: 50,
483
        itemBuilder: (BuildContext context, int index) {
484
          if (index == 0) {
485
            return const _AlwaysKeepAlive(
486
              key: GlobalObjectKey<_AlwaysKeepAliveState>(0),
487 488
            );
          }
489
          return SizedBox(
490
            height: 44.0,
491
            child: Text('FooBar $index'),
492 493 494 495 496 497 498 499 500 501 502 503
          );
        },
      ),
    ));

    expect(find.text('keep me alive'), findsOneWidget);
    expect(find.text('FooBar 1'), findsOneWidget);
    expect(find.text('FooBar 2'), findsOneWidget);

    expect(find.byKey(const GlobalObjectKey<_AlwaysKeepAliveState>(0)), findsOneWidget);
    await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0)); // move to bottom
    await tester.pump();
504
    expect(find.byKey(const GlobalObjectKey<_AlwaysKeepAliveState>(0), skipOffstage: false), findsOneWidget);
505

506
    expect(find.text('keep me alive', skipOffstage: false), findsOneWidget);
507 508 509
    expect(find.text('FooBar 1'), findsNothing);
    expect(find.text('FooBar 2'), findsNothing);
  });
510 511

  testWidgets('AutomaticKeepAlive with keepAlive set to true before initState and widget goes out of scope', (WidgetTester tester) async {
512
    await tester.pumpWidget(Directionality(
513
      textDirection: TextDirection.ltr,
514
      child: ListView.builder(
515
        addSemanticIndexes: false,
516
        itemCount: 250,
517
        itemBuilder: (BuildContext context, int index) {
518
          if (index.isEven) {
519
            return _AlwaysKeepAlive(
520 521 522
              key: GlobalObjectKey<_AlwaysKeepAliveState>(index),
            );
          }
523
          return SizedBox(
524
            height: 44.0,
525
            child: Text('FooBar $index'),
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548
          );
        },
      ),
    ));

    expect(find.text('keep me alive'), findsNWidgets(7));
    expect(find.text('FooBar 1'), findsOneWidget);
    expect(find.text('FooBar 3'), findsOneWidget);

    expect(find.byKey(const GlobalObjectKey<_AlwaysKeepAliveState>(0)), findsOneWidget);

    final ScrollableState state = tester.state(find.byType(Scrollable));
    final ScrollPosition position = state.position;
    position.jumpTo(3025.0);

    await tester.pump();
    expect(find.byKey(const GlobalObjectKey<_AlwaysKeepAliveState>(0), skipOffstage: false), findsOneWidget);

    expect(find.text('keep me alive', skipOffstage: false), findsNWidgets(23));
    expect(find.text('FooBar 1'), findsNothing);
    expect(find.text('FooBar 3'), findsNothing);
    expect(find.text('FooBar 73'), findsOneWidget);
  });
549 550 551 552 553 554 555 556 557 558 559

  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);
  });
560 561 562
}

class _AlwaysKeepAlive extends StatefulWidget {
563
  const _AlwaysKeepAlive({ required Key key }) : super(key: key);
564 565

  @override
566
  State<StatefulWidget> createState() => _AlwaysKeepAliveState();
567 568 569 570 571 572 573 574 575
}

class _AlwaysKeepAliveState extends State<_AlwaysKeepAlive> with AutomaticKeepAliveClientMixin<_AlwaysKeepAlive> {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
576
    return const SizedBox(
577
      height: 48.0,
578
      child: Text('keep me alive'),
579 580 581
    );
  }
}
582 583 584 585 586 587 588 589 590 591 592 593

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);
594
    return const SizedBox(
595
      height: 48.0,
596
      child: Text('keep me alive'),
597 598 599 600
    );
  }
}

601
mixin KeepAliveParentDataMixinAlt implements KeepAliveParentDataMixin {
602 603 604 605 606 607 608 609 610 611 612 613 614
  @override
  bool keptAlive = false;

  @override
  bool keepAlive = false;
}

class RenderSliverMultiBoxAdaptorAlt extends RenderSliver with
    KeepAliveParentDataMixinAlt,
    RenderSliverHelpers,
    RenderSliverWithKeepAliveMixin {

  RenderSliverMultiBoxAdaptorAlt({
615
    RenderSliverBoxChildManager? childManager,
616 617 618
  }) : _childManager = childManager;

  @protected
619 620
  RenderSliverBoxChildManager? get childManager => _childManager;
  final RenderSliverBoxChildManager? _childManager;
621 622 623

  final List<RenderBox> children = <RenderBox>[];

624
  void insert(RenderBox child, { RenderBox? after }) {
625 626 627 628 629 630 631 632 633
    children.add(child);
  }

  @override
  void visitChildren(RenderObjectVisitor visitor) {
    children.forEach(visitor);
  }

  @override
634
  void performLayout() { }
635
}