automatic_keep_alive_test.dart 29.1 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/rendering.dart';
6 7
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
8
import 'package:flutter/gestures.dart' show DragStartBehavior;
9 10

class Leaf extends StatefulWidget {
11
  const Leaf({ required Key key, required this.child }) : super(key: key);
12 13
  final Widget child;
  @override
14
  _LeafState 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 59 60
        child: child,
      );
      if (impliedMode)
        return result;
61
      return AutomaticKeepAlive(child: result);
62 63 64 65 66
    },
    growable: false,
  );
}

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

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

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

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

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

307
  testWidgets('AutomaticKeepAlive double 2', (WidgetTester tester) async {
308
    await tester.pumpWidget(
309
      Directionality(
310
        textDirection: TextDirection.ltr,
311
        child: ListView(
312 313
          addAutomaticKeepAlives: false,
          addRepaintBoundaries: false,
314
          addSemanticIndexes: false,
315
          cacheExtent: 0.0,
316
          children: <Widget>[
317 318
            AutomaticKeepAlive(
              child: Container(
319
                height: 400.0,
320
                child: Stack(children: const <Widget>[
321 322
                  Leaf(key: GlobalObjectKey<_LeafState>(0), child: Placeholder()),
                  Leaf(key: GlobalObjectKey<_LeafState>(1), child: Placeholder()),
323 324
                ]),
              ),
325
            ),
326 327
            AutomaticKeepAlive(
              child: Container(
328
                height: 400.0,
329
                child: Stack(children: const <Widget>[
330 331
                  Leaf(key: GlobalObjectKey<_LeafState>(2), child: Placeholder()),
                  Leaf(key: GlobalObjectKey<_LeafState>(3), child: Placeholder()),
332 333
                ]),
              ),
334
            ),
335 336
            AutomaticKeepAlive(
              child: Container(
337
                height: 400.0,
338
                child: Stack(children: const <Widget>[
339 340
                  Leaf(key: GlobalObjectKey<_LeafState>(4), child: Placeholder()),
                  Leaf(key: GlobalObjectKey<_LeafState>(5), child: Placeholder()),
341 342
                ]),
              ),
343
            ),
344 345
          ],
        ),
346
      ),
347
    );
348 349 350 351
    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);
352 353
    expect(find.byKey(const GlobalObjectKey<_LeafState>(4), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(5), skipOffstage: false), findsNothing);
354
    const GlobalObjectKey<_LeafState>(0).currentState!.setKeepAlive(true);
355 356
    await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0)); // move to bottom
    await tester.pump();
357 358 359 360
    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);
361 362 363 364
    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);
365
    await tester.pumpWidget(Directionality(
366
      textDirection: TextDirection.ltr,
367
      child: ListView(
368 369
        addAutomaticKeepAlives: false,
        addRepaintBoundaries: false,
370
        addSemanticIndexes: false,
371
        cacheExtent: 0.0,
372
        children: <Widget>[
373 374
          AutomaticKeepAlive(
            child: Container(
375
              height: 400.0,
376
              child: Stack(children: const <Widget>[
377
                Leaf(key: GlobalObjectKey<_LeafState>(1), child: Placeholder()),
378 379
              ]),
            ),
380
          ),
381 382
          AutomaticKeepAlive(
            child: Container(
383
              height: 400.0,
384
              child: Stack(children: const <Widget>[
385 386
                Leaf(key: GlobalObjectKey<_LeafState>(2), child: Placeholder()),
                Leaf(key: GlobalObjectKey<_LeafState>(3), child: Placeholder()),
387 388
              ]),
            ),
389
          ),
390 391
          AutomaticKeepAlive(
            child: Container(
392
              height: 400.0,
393
              child: Stack(children: const <Widget>[
394 395 396
                Leaf(key: GlobalObjectKey<_LeafState>(4), child: Placeholder()),
                Leaf(key: GlobalObjectKey<_LeafState>(5), child: Placeholder()),
                Leaf(key: GlobalObjectKey<_LeafState>(0), child: Placeholder()),
397 398
              ]),
            ),
399
          ),
400 401
        ],
      ),
402 403
    ));
    await tester.pump(); // Sometimes AutomaticKeepAlive needs an extra pump to clean things up.
404
    expect(find.byKey(const GlobalObjectKey<_LeafState>(1), skipOffstage: false), findsNothing);
405 406 407 408 409 410 411 412 413 414
    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);
415 416 417 418 419 420
    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);
421
    const GlobalObjectKey<_LeafState>(0).currentState!.setKeepAlive(false);
422 423 424 425
    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);
426 427 428
    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);
429
    await tester.pumpWidget(Directionality(
430
      textDirection: TextDirection.ltr,
431
      child: ListView(
432 433
        addAutomaticKeepAlives: false,
        addRepaintBoundaries: false,
434
        addSemanticIndexes: false,
435
        cacheExtent: 0.0,
436
        children: <Widget>[
437 438
          AutomaticKeepAlive(
            child: Container(
439
              height: 400.0,
440
              child: Stack(children: const <Widget>[
441 442
                Leaf(key: GlobalObjectKey<_LeafState>(1), child: Placeholder()),
                Leaf(key: GlobalObjectKey<_LeafState>(2), child: Placeholder()),
443 444
              ]),
            ),
445
          ),
446 447
          AutomaticKeepAlive(
            child: Container(
448
              height: 400.0,
449
              child: Stack(children: const <Widget>[
450 451
              ]),
            ),
452
          ),
453 454
          AutomaticKeepAlive(
            child: Container(
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 Container(
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 Container(
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 Container(
577 578 579 580 581
      height: 48.0,
      child: const Text('keep me alive'),
    );
  }
}
582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614

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({
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
}