Skip to content

TableView cacheExtent Only Considers Items In The Forward Direction #167813

@psengelke

Description

@psengelke

What package does this bug report belong to?

two_dimensional_scrollables

What target platforms are you seeing this bug on?

Android, macOS, iOS, Web, Linux, Windows

Have you already upgraded your packages?

Yes

Dependency versions

pubspec.lock
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
  async:
    dependency: transitive
    description:
      name: async
      sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
      url: "https://pub.dev"
    source: hosted
    version: "2.12.0"
  boolean_selector:
    dependency: transitive
    description:
      name: boolean_selector
      sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
      url: "https://pub.dev"
    source: hosted
    version: "2.1.2"
  characters:
    dependency: transitive
    description:
      name: characters
      sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
      url: "https://pub.dev"
    source: hosted
    version: "1.4.0"
  clock:
    dependency: transitive
    description:
      name: clock
      sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
      url: "https://pub.dev"
    source: hosted
    version: "1.1.2"
  collection:
    dependency: transitive
    description:
      name: collection
      sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
      url: "https://pub.dev"
    source: hosted
    version: "1.19.1"
  cupertino_icons:
    dependency: "direct main"
    description:
      name: cupertino_icons
      sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
      url: "https://pub.dev"
    source: hosted
    version: "1.0.8"
  fake_async:
    dependency: transitive
    description:
      name: fake_async
      sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
      url: "https://pub.dev"
    source: hosted
    version: "1.3.2"
  flutter:
    dependency: "direct main"
    description: flutter
    source: sdk
    version: "0.0.0"
  flutter_lints:
    dependency: "direct dev"
    description:
      name: flutter_lints
      sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
      url: "https://pub.dev"
    source: hosted
    version: "5.0.0"
  flutter_test:
    dependency: "direct dev"
    description: flutter
    source: sdk
    version: "0.0.0"
  leak_tracker:
    dependency: transitive
    description:
      name: leak_tracker
      sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
      url: "https://pub.dev"
    source: hosted
    version: "10.0.8"
  leak_tracker_flutter_testing:
    dependency: transitive
    description:
      name: leak_tracker_flutter_testing
      sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
      url: "https://pub.dev"
    source: hosted
    version: "3.0.9"
  leak_tracker_testing:
    dependency: transitive
    description:
      name: leak_tracker_testing
      sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
      url: "https://pub.dev"
    source: hosted
    version: "3.0.1"
  lints:
    dependency: transitive
    description:
      name: lints
      sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
      url: "https://pub.dev"
    source: hosted
    version: "5.1.1"
  matcher:
    dependency: transitive
    description:
      name: matcher
      sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
      url: "https://pub.dev"
    source: hosted
    version: "0.12.17"
  material_color_utilities:
    dependency: transitive
    description:
      name: material_color_utilities
      sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
      url: "https://pub.dev"
    source: hosted
    version: "0.11.1"
  meta:
    dependency: transitive
    description:
      name: meta
      sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
      url: "https://pub.dev"
    source: hosted
    version: "1.16.0"
  path:
    dependency: transitive
    description:
      name: path
      sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
      url: "https://pub.dev"
    source: hosted
    version: "1.9.1"
  sky_engine:
    dependency: transitive
    description: flutter
    source: sdk
    version: "0.0.0"
  source_span:
    dependency: transitive
    description:
      name: source_span
      sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
      url: "https://pub.dev"
    source: hosted
    version: "1.10.1"
  stack_trace:
    dependency: transitive
    description:
      name: stack_trace
      sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
      url: "https://pub.dev"
    source: hosted
    version: "1.12.1"
  stream_channel:
    dependency: transitive
    description:
      name: stream_channel
      sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
      url: "https://pub.dev"
    source: hosted
    version: "2.1.4"
  string_scanner:
    dependency: transitive
    description:
      name: string_scanner
      sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
      url: "https://pub.dev"
    source: hosted
    version: "1.4.1"
  term_glyph:
    dependency: transitive
    description:
      name: term_glyph
      sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
      url: "https://pub.dev"
    source: hosted
    version: "1.2.2"
  test_api:
    dependency: transitive
    description:
      name: test_api
      sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
      url: "https://pub.dev"
    source: hosted
    version: "0.7.4"
  two_dimensional_scrollables:
    dependency: "direct main"
    description:
      name: two_dimensional_scrollables
      sha256: b6028c80e782e58a5d18f9491737aae4f70d72dc08050ac92006905c7c0b5e21
      url: "https://pub.dev"
    source: hosted
    version: "0.3.3"
  vector_math:
    dependency: transitive
    description:
      name: vector_math
      sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
      url: "https://pub.dev"
    source: hosted
    version: "2.1.4"
  vm_service:
    dependency: transitive
    description:
      name: vm_service
      sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
      url: "https://pub.dev"
    source: hosted
    version: "14.3.1"
sdks:
  dart: ">=3.7.2 <4.0.0"
  flutter: ">=3.19.0"

Steps to reproduce

  1. Start the example code on any platform.
  2. Scroll in the forward direction in the "TableView" tab.
  3. Observe that items turn green after 1 second (or were mounted off-screen and are already green).
  4. Scroll in the reverse direction and observe that those items immediately start in their initial "red" state.
  5. Scrolling forward again, the items that first appear are already green (as they were cached).
  6. Switch to the "GridView" tab and repeat steps 1-4, but observe that the first few items in both directions remain green.

Expected results

TableView.cacheExtent should behave similarly to other Flutter list widgets such as GridView as demonstrated. cacheExtent should consider items both before and after the visible region, and not those after exclusively.

Actual results

Items in the region before the visible region in TableView are immediately discarded and must be recreated every time they reappear.

Code sample

Code sample
import 'package:flutter/material.dart';
import 'package:two_dimensional_scrollables/two_dimensional_scrollables.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.white),
      ),
      home: const HomePage(),
    );
  }
}

enum HomePageView { tableView, gridList }

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int _index = 0;

  Widget _body() => switch (_index) {
    0 => _TableView(),
    1 => _GridView(),
    _ => Container(),
  };

  BottomNavigationBar _navbar() => BottomNavigationBar(
    currentIndex: _index,
    onTap: (index) {
      setState(() {
        _index = index;
      });
    },
    items: const [
      BottomNavigationBarItem(
        icon: Icon(Icons.table_chart),
        label: 'TableView',
      ),
      BottomNavigationBarItem(icon: Icon(Icons.grid_on), label: 'GridView'),
    ],
  );

  @override
  Widget build(BuildContext context) {
    return Scaffold(body: _body(), bottomNavigationBar: _navbar());
  }
}

class _TableView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return TableView.builder(
      rowBuilder: (index) => TableSpan(extent: FixedTableSpanExtent(100)),
      columnBuilder: (index) => TableSpan(extent: FixedTableSpanExtent(100)),
      cellBuilder: (context, vicinity) {
        final row = vicinity.row;
        final column = vicinity.column;
        return TableViewCell(child: _TableCell(x: row, y: column));
      },
      rowCount: 50,
      columnCount: 50,
      cacheExtent: 5 * 100,
      diagonalDragBehavior: DiagonalDragBehavior.free,
    );
  }
}

class _GridView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      cacheExtent: 5 * 100,
      gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
        maxCrossAxisExtent: 100,
      ),
      itemCount: 50 * 50,
      itemBuilder: (context, index) {
        final row = index ~/ 50;
        final column = index % 20;
        return _TableCell(x: row, y: column);
      },
    );
  }
}

class _TableCell extends StatefulWidget {
  final int x;
  final int y;

  const _TableCell({required this.x, required this.y});

  @override
  State<_TableCell> createState() => _TableCellState();
}

class _TableCellState extends State<_TableCell> {
  bool _mounted = false;

  @override
  void initState() {
    super.initState();
    Future.delayed(const Duration(seconds: 1), () {
      if (mounted) {
        setState(() {
          _mounted = true;
        });
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.center,
      decoration: BoxDecoration(color: _mounted ? Colors.green : Colors.red),
      child: Text('${widget.x}:${widget.y}'),
    );
  }
}

Screenshots or Videos

Screenshots / Video demonstration
cacheExtentIssue.mov

Logs

Logs

logs.txt

Flutter Doctor output

Doctor output
[✓] Flutter (Channel stable, 3.29.3, on macOS 15.4.1 24E263 darwin-arm64, locale en-ZA) [1,132ms]
    • Flutter version 3.29.3 on channel stable at /Users/psengelke/bin/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision ea121f8859 (2 weeks ago), 2025-04-11 19:10:07 +0000
    • Engine revision cf56914b32
    • Dart version 3.7.2
    • DevTools version 2.42.3

[✓] Android toolchain - develop for Android devices (Android SDK version 35.0.0) [4.6s]
    • Android SDK at /Users/psengelke/Library/Android/sdk
    • Platform android-35, build-tools 35.0.0
    • ANDROID_HOME = /Users/psengelke/Library/Android/sdk
    • Java binary at: /Users/psengelke/Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
      This is the JDK bundled with the latest Android Studio installation on this machine.
      To manually set the JDK path, use: `flutter config --jdk-dir="path/to/jdk"`.
    • Java version OpenJDK Runtime Environment (build 21.0.6+-13355223-b631.42)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 16.3) [1,114ms]
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 16E140
    • CocoaPods version 1.16.2

[✓] Chrome - develop for the web [25ms]
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2024.3) [25ms]
    • Android Studio at /Users/psengelke/Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 21.0.6+-13355223-b631.42)

[✓] IntelliJ IDEA Ultimate Edition (version 2025.1) [24ms]
    • IntelliJ at /Users/psengelke/Applications/IntelliJ IDEA Ultimate.app
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart

[✓] VS Code (version 1.99.3) [8ms]
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.108.0

[✓] Connected device (3 available) [6.2s]
    • macOS (desktop)                 • macos                 • darwin-arm64   • macOS 15.4.1 24E263 darwin-arm64
    • Mac Designed for iPad (desktop) • mac-designed-for-ipad • darwin         • macOS 15.4.1 24E263 darwin-arm64
    • Chrome (web)                    • chrome                • web-javascript • Google Chrome 135.0.7049.115

[✓] Network resources [710ms]
    • All expected network resources are available.

• No issues found!

Metadata

Metadata

Assignees

Labels

P2Important issues not at the top of the work listfound in release: 3.29Found to occur in 3.29found in release: 3.32Found to occur in 3.32has reproducible stepsThe issue has been confirmed reproducible and is ready to work onp: two_dimensional_scrollablesIssues pertaining to the two_dimensional_scrollables packagepackageflutter/packages repository. See also p: labels.team-frameworkOwned by Framework teamtriaged-frameworkTriaged by Framework team

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions