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
This is expected, because the moment
userHasSlided
changed, the children of thatListView
changed too, and this triggers a widget rebuild. Since thePageView
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 theAppBar
, and remove the conditional text in theListView
, and you’ll notice that thePageView
will work as expected. It is because thePageView
is not being rebuilt.For cases like this, providing
key
helps the element tree .More about Keys.