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 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
            const SizedBox(height: 12.3, child: 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
            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
            AutomaticKeepAlive(
234
              child: SizedBox(
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 244
            const AutomaticKeepAlive(
              child: SizedBox(
                key: GlobalObjectKey<_LeafState>(2),
245 246
                height: 400.0,
              ),
247
            ),
248 249 250
            const AutomaticKeepAlive(
              child: SizedBox(
                key: GlobalObjectKey<_LeafState>(3),
251 252
                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
            AutomaticKeepAlive(
318
              child: SizedBox(
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
            AutomaticKeepAlive(
327
              child: SizedBox(
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
            AutomaticKeepAlive(
336
              child: SizedBox(
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
          AutomaticKeepAlive(
374
            child: SizedBox(
375
              height: 400.0,
376
              child: Stack(children: const <Widget>[
377
                Leaf(key: GlobalObjectKey<_LeafState>(1), child: Placeholder()),
378 379
              ]),
            ),
380
          ),
381
          AutomaticKeepAlive(
382
            child: SizedBox(
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
          AutomaticKeepAlive(
391
            child: SizedBox(
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
          AutomaticKeepAlive(
438
            child: SizedBox(
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
          AutomaticKeepAlive(
447
            child: SizedBox(
448
              height: 400.0,
449
              child: Stack(),
450
            ),
451
          ),
452
          AutomaticKeepAlive(
453
            child: SizedBox(
454
              height: 400.0,
455
              child: Stack(children: const <Widget>[
456 457 458 459
                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()),
460 461
              ]),
            ),
462
          ),
463 464
        ],
      ),
465 466 467 468
    ));
    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);
469 470 471 472
    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);
473
  });
474 475

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

    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();
503
    expect(find.byKey(const GlobalObjectKey<_AlwaysKeepAliveState>(0), skipOffstage: false), findsOneWidget);
504

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

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

    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);
  });
548 549 550 551 552 553 554 555 556 557 558

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

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

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

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

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

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

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

  @override
  bool keepAlive = false;
}

class RenderSliverMultiBoxAdaptorAlt extends RenderSliver with
    KeepAliveParentDataMixinAlt,
    RenderSliverHelpers,
    RenderSliverWithKeepAliveMixin {

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

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

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

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

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

  @override
633
  void performLayout() { }
634
}