automatic_keep_alive_test.dart 28.9 KB
Newer Older
1 2 3 4
// Copyright 2017 The Chromium Authors. All rights reserved.
// 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 8 9
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';

class Leaf extends StatefulWidget {
10
  const Leaf({ Key key, this.child }) : super(key: key);
11 12
  final Widget child;
  @override
13
  _LeafState createState() => _LeafState();
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
}

class _LeafState extends State<Leaf> {
  bool _keepAlive = false;
  KeepAliveHandle _handle;

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

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

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

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

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

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

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

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

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

306
  testWidgets('AutomaticKeepAlive double 2', (WidgetTester tester) async {
307
    await tester.pumpWidget(
308
      Directionality(
309
        textDirection: TextDirection.ltr,
310
        child: ListView(
311 312
          addAutomaticKeepAlives: false,
          addRepaintBoundaries: false,
313
          addSemanticIndexes: false,
314
          cacheExtent: 0.0,
315
          children: <Widget>[
316 317
            AutomaticKeepAlive(
              child: Container(
318
                height: 400.0,
319
                child: Stack(children: const <Widget>[
320 321
                  Leaf(key: GlobalObjectKey<_LeafState>(0), child: Placeholder()),
                  Leaf(key: GlobalObjectKey<_LeafState>(1), child: Placeholder()),
322 323
                ]),
              ),
324
            ),
325 326
            AutomaticKeepAlive(
              child: Container(
327
                height: 400.0,
328
                child: Stack(children: const <Widget>[
329 330
                  Leaf(key: GlobalObjectKey<_LeafState>(2), child: Placeholder()),
                  Leaf(key: GlobalObjectKey<_LeafState>(3), child: Placeholder()),
331 332
                ]),
              ),
333
            ),
334 335
            AutomaticKeepAlive(
              child: Container(
336
                height: 400.0,
337
                child: Stack(children: const <Widget>[
338 339
                  Leaf(key: GlobalObjectKey<_LeafState>(4), child: Placeholder()),
                  Leaf(key: GlobalObjectKey<_LeafState>(5), child: Placeholder()),
340 341
                ]),
              ),
342
            ),
343 344
          ],
        ),
345
      ),
346
    );
347 348 349 350
    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);
351 352
    expect(find.byKey(const GlobalObjectKey<_LeafState>(4), skipOffstage: false), findsNothing);
    expect(find.byKey(const GlobalObjectKey<_LeafState>(5), skipOffstage: false), findsNothing);
353 354 355
    const GlobalObjectKey<_LeafState>(0).currentState.setKeepAlive(true);
    await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0)); // move to bottom
    await tester.pump();
356 357 358 359
    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);
360 361 362 363
    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);
364
    await tester.pumpWidget(Directionality(
365
      textDirection: TextDirection.ltr,
366
      child: ListView(
367 368
        addAutomaticKeepAlives: false,
        addRepaintBoundaries: false,
369
        addSemanticIndexes: false,
370
        cacheExtent: 0.0,
371
        children: <Widget>[
372 373
          AutomaticKeepAlive(
            child: Container(
374
              height: 400.0,
375
              child: Stack(children: const <Widget>[
376
                Leaf(key: GlobalObjectKey<_LeafState>(1), child: Placeholder()),
377 378
              ]),
            ),
379
          ),
380 381
          AutomaticKeepAlive(
            child: Container(
382
              height: 400.0,
383
              child: Stack(children: const <Widget>[
384 385
                Leaf(key: GlobalObjectKey<_LeafState>(2), child: Placeholder()),
                Leaf(key: GlobalObjectKey<_LeafState>(3), child: Placeholder()),
386 387
              ]),
            ),
388
          ),
389 390
          AutomaticKeepAlive(
            child: Container(
391
              height: 400.0,
392
              child: Stack(children: const <Widget>[
393 394 395
                Leaf(key: GlobalObjectKey<_LeafState>(4), child: Placeholder()),
                Leaf(key: GlobalObjectKey<_LeafState>(5), child: Placeholder()),
                Leaf(key: GlobalObjectKey<_LeafState>(0), child: Placeholder()),
396 397
              ]),
            ),
398
          ),
399 400
        ],
      ),
401 402
    ));
    await tester.pump(); // Sometimes AutomaticKeepAlive needs an extra pump to clean things up.
403
    expect(find.byKey(const GlobalObjectKey<_LeafState>(1), skipOffstage: false), findsNothing);
404 405 406 407 408 409 410 411 412 413
    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);
414 415 416 417 418 419
    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);
420 421 422 423 424
    const GlobalObjectKey<_LeafState>(0).currentState.setKeepAlive(false);
    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);
425 426 427
    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);
428
    await tester.pumpWidget(Directionality(
429
      textDirection: TextDirection.ltr,
430
      child: ListView(
431 432
        addAutomaticKeepAlives: false,
        addRepaintBoundaries: false,
433
        addSemanticIndexes: false,
434
        cacheExtent: 0.0,
435
        children: <Widget>[
436 437
          AutomaticKeepAlive(
            child: Container(
438
              height: 400.0,
439
              child: Stack(children: const <Widget>[
440 441
                Leaf(key: GlobalObjectKey<_LeafState>(1), child: Placeholder()),
                Leaf(key: GlobalObjectKey<_LeafState>(2), child: Placeholder()),
442 443
              ]),
            ),
444
          ),
445 446
          AutomaticKeepAlive(
            child: Container(
447
              height: 400.0,
448
              child: Stack(children: const <Widget>[
449 450
              ]),
            ),
451
          ),
452 453
          AutomaticKeepAlive(
            child: Container(
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
        addSemanticIndexes: false,
480 481 482 483
        itemCount: 50,
        itemBuilder: (BuildContext context, int index){
          if (index == 0){
            return const _AlwaysKeepAlive(
484
              key: GlobalObjectKey<_AlwaysKeepAliveState>(0),
485 486
            );
          }
487
          return Container(
488
            height: 44.0,
489
            child: Text('FooBar $index'),
490 491 492 493 494 495 496 497 498 499 500 501
          );
        },
      ),
    ));

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

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

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

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

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

class _AlwaysKeepAlive extends StatefulWidget {
  const _AlwaysKeepAlive({Key key}) : super(key: key);

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

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

  @override
  Widget build(BuildContext context) {
    super.build(context);
574
    return Container(
575 576 577 578 579
      height: 48.0,
      child: const Text('keep me alive'),
    );
  }
}
580 581 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 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633

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({
    RenderSliverBoxChildManager childManager
  }) : _childManager = childManager;

  @protected
  RenderSliverBoxChildManager get childManager => _childManager;
  final RenderSliverBoxChildManager _childManager;

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

  void insert(RenderBox child, { RenderBox after }) {
    children.add(child);
  }

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

  @override
  void performLayout() {}
}