Skip to content

Flinging in PageView depends on rendering time, leading to inconsistent behaviour #174904

@Eerey

Description

@Eerey

Steps to reproduce

Run the example code.

Expected results

Heavy Build: disabled and Heavy Build: enabled (shown in the example) should not differ in their behaviour.

The flinging should work, regardless of long build times.

Actual results

Heavy Build: enabled flings back to original page, even when flinging with great velocity. This is undesired.

Code sample

Code sample
import 'dart:math';

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'PageView Bug',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
      ),
      home: ScrollConfiguration(
        behavior: ScrollConfiguration.of(context).copyWith(
          // This is required to swipe left and right on windows
          dragDevices: {PointerDeviceKind.touch, PointerDeviceKind.mouse},
        ),
        child: Scaffold(
          appBar: AppBar(
            title: Text(
              "PageView Bug - Fling not working correctly if Page takes more time to build",
            ),
          ),
          body: Column(children: [Expanded(child: Content())]),
        ),
      ),
    );
  }
}

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

  @override
  State<Content> createState() => _ContentState();
}

class _ContentState extends State<Content> {
  ValueNotifier<bool> heavyVn = ValueNotifier<bool>(true);

  final ValueNotifier<String> startEventVn = ValueNotifier<String>(
    "ScrollStartNotification: -",
  );
  final ValueNotifier<String> userEventVn = ValueNotifier<String>(
    "UserScrollNotification: -",
  );
  final ValueNotifier<String> updateEventVn = ValueNotifier<String>(
    "ScrollUpdateNotification: -",
  );
  final ValueNotifier<String> endEventVn = ValueNotifier<String>(
    "ScrollEndNotification: -",
  );

  void _handleScrollNotification(ScrollNotification notification) {
    if (notification is ScrollStartNotification) {
      startEventVn.value =
          "ScrollStartNotification → Dragging=${notification.dragDetails != null}";
    } else if (notification is UserScrollNotification) {
      userEventVn.value =
          "UserScrollNotification → Direction=${notification.direction}";
    } else if (notification is ScrollUpdateNotification) {
      updateEventVn.value =
          "ScrollUpdateNotification → Δ=${notification.scrollDelta?.toStringAsFixed(2)}";
    } else if (notification is ScrollEndNotification) {
      endEventVn.value = "ScrollEndNotification → ${notification.toString()}";
    }
  }

  void reset() {
    startEventVn.value = "ScrollStartNotification →";
    userEventVn.value = "UserScrollNotification →";
    updateEventVn.value = "ScrollUpdateNotification →";
    endEventVn.value = "ScrollEndNotification →";
  }

  Widget _buildEventText(ValueNotifier<String> vn) {
    return Padding(
      padding: const EdgeInsets.all(4.0),
      child: ValueListenableBuilder<String>(
        valueListenable: vn,
        builder: (context, value, _) => Text(
          value,
          style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<bool>(
      valueListenable: heavyVn,
      builder: (context, enableExpensiveBuildTime, child) => Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          _buildEventText(startEventVn),
          _buildEventText(userEventVn),
          _buildEventText(updateEventVn),
          _buildEventText(endEventVn),
          Row(
            children: [
              Text("Heavy Build: ${heavyVn.value ? "enabled" : "disabled"}"),
              Checkbox(
                value: heavyVn.value,
                onChanged: (value) {
                  reset();
                  heavyVn.value = value ?? true;
                },
              ),
            ],
          ),
          const Divider(),
          Expanded(
            child: NotificationListener<ScrollNotification>(
              onNotification: (notification) {
                _handleScrollNotification(notification);
                return true;
              },
              child: FlutterPageView(heavy: enableExpensiveBuildTime),
            ),
          ),
        ],
      ),
    );
  }
}

class FlutterPageView extends StatelessWidget {
  const FlutterPageView({super.key, required this.heavy});

  final bool heavy;

  @override
  Widget build(BuildContext context) {
    // this is purposefully inefficient
    final list = List<int>.generate(
      heavy ? 5000 : 10,
      (int index) => index,
      growable: false,
    );
    return PageView.builder(
      itemBuilder: (context, index) {
        return Container(
          color: Colors.blue[(index * 100) % 1000],
          child: Stack(
            children: [
              for (final item in list)
                Positioned(
                  left: item.toDouble() * 10,
                  top: 120 + sin(item * 0.4) * 120 + item * 2.05,
                  child: Text(
                    index.toString(),
                    style: TextStyle(color: Colors.black, fontSize: item % 164),
                  ),
                ),
            ],
          ),
        );
      },
      itemCount: 20,
    );
  }
}

Screenshots or Video

page_view_corrupted.2025-09-03.22-33-46.mp4

Logs

Logs
[Paste your logs here]

Flutter Doctor output

Doctor output
[√] Flutter (Channel stable, 3.35.2, on Microsoft Windows [Version 10.0.19045.3208], locale de-DE)
[√] Windows Version (10 64-Bit)
[√] Android toolchain - develop for Android devices (Android SDK version 35.0.1)
[√] Chrome - develop for the web
[√] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.5.2)  
[√] Android Studio (version 2024.3)
[√] VS Code, 64-bit edition (version 1.103.2)
[√] Connected device (3 available)
[√] Network resources

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work listc: performanceRelates to speed or footprint issues (see "perf:" labels)f: gesturesflutter/packages/flutter/gestures repository.found in release: 3.35Found to occur in 3.35found in release: 3.36Found to occur in 3.36frameworkflutter/packages/flutter repository. See also f: labels.has reproducible stepsThe issue has been confirmed reproducible and is ready to work onperf: speedPerformance issues related to (mostly rendering) speedteam-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