skip to Main Content

Above the first Page that shows, I show a message that the user can swipe left to go to the next page. In onPageChanged I set a bool to dismiss the message and call setState. When I swipe to page 1, at the moment setState is called, the page resets to page 0.

When I remove the conditional text, the page swipes normally.

DartPad gist: https://dartpad.dev/?id=e30a98e4bc531d9cdc93780b6e47f972

Is this a bug or do I miss something?

Full example code below:

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

void main() {
  runApp(PageViewTestScreen());
}

class PageViewTestScreen extends StatefulWidget {
  PageViewTestScreen({super.key});

  @override
  State<PageViewTestScreen> createState() => _PageViewTestScreenState();
}

class _PageViewTestScreenState extends State<PageViewTestScreen> {
  PageController pageController = PageController(initialPage: 0);
  bool lastPage = false;
  bool userHasSlided = false;
  int numberOfPages = 5;

  @override
  void initState() {
    super.initState();
  }

  void onPageChanged(int index) {
    userHasSlided = true;

    lastPage = index == levensgebieden.length-1;
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        floatingActionButton: lastPage
            ? FloatingActionButton(
                onPressed: () {},
                child: Icon(Icons.check),
              )
            : null,
        appBar: AppBar(
          foregroundColor: Colors.white,
          title: const Text("PageView bug?"),
        ),
        body: ListView(
          children: [
            const Text("Here are some pages"),
            const SizedBox(height: 6),
            if (!userHasSlided) const Text("Swipe left to continue."),
            SizedBox(
              height: 500,
              child: PageView(
                scrollBehavior: const ScrollBehavior().copyWith(dragDevices: {
                  PointerDeviceKind.touch,
                  PointerDeviceKind.mouse,
                  PointerDeviceKind.stylus,
                }),
                controller: pageController,
                onPageChanged: (int index) {
                  onPageChanged(index);
                },
                children: [
                  for (int i = 0; i < 5; i++) card(i),
                ],
              ),
            )
          ],
        ),
      ),
    );
  }

  Widget card(int index) {
    return Card(
      child: SizedBox(
          height: 500, width: 200, child: Center(child: Text("Page: $index"))),
    );
  }
}

2

Answers


  1. This is expected, because the moment userHasSlided changed, the children of that ListView changed too, and this triggers a widget rebuild. Since the PageView is not a constant, it’s being rebuilt as well. The rebuild interrupts the sliding progress that the user has made and therefore resetting the page to 0.

    You can try adding the conditional case elsewhere (outside the ListView), perhaps in the AppBar, and remove the conditional text in the ListView, and you’ll notice that the PageView will work as expected. It is because the PageView is not being rebuilt.

    Login or Signup to reply.
  2. For cases like this, providing key helps the element tree .

    SizedBox(
      key: const Key("my_pageView"),
      height: 500,
      child: PageView(
    

    More about Keys.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search