flutterdartflutter-layoutflutter-listviewflutter-pageview

Flutter Setstate called multiple times (GestureDetector & PageView)


basically I have a swiping screen with elements, where user is able to swipe in left or right direction. When the user is swiping, im calling some functions. Im using GestureDetector for gesture recognitions and PageView.Custom for my items. Probably ListView.Custom does also work, but it doesn't fix my issue I have.

I need a PageController, because I have to control the navigation programatically. And I think the PageController maybe is the reason behind my issue that my functions are called multiple times. How to fix it? Does somebody know why setstate is called that often and what to do to prevent it?

Im providing you a fully working example (minified version) with a print on the swiping right actions, where you can see that its beeing called multiple times.

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

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {


  int currentIndex = 0;
  @override
  Widget build(BuildContext context) {
    // Page selector for tab list
    void _selectPage(int index) {
      print('page index: $index');
      setState(() {
        currentIndex = index;
      });
    }


    // Routes list for tab navigation Android
    final List<Widget> _pages = [
      ScreenA(),
      ScreenB(func: _selectPage),
    ];

    return Scaffold(
      appBar: AppBar(),
      body: _pages[currentIndex],
      bottomNavigationBar: SafeArea(
        child: BottomNavigationBar(
          onTap: _selectPage,
          iconSize: 22,
          currentIndex: currentIndex,
          type: BottomNavigationBarType.fixed,
          items: [
            BottomNavigationBarItem(
              backgroundColor: Theme.of(context).primaryColor,
              icon: Icon(Icons.description),
              label: 'ScreenA',
            ),
            BottomNavigationBarItem(
                backgroundColor: Theme.of(context).primaryColor,
                icon: Icon(Icons.ac_unit_outlined),
                label: 'ScreenB'),
          ],
        ),
      ),
    );
  }
}

class ScreenA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text('HOME'),
    );
  }
}

class ScreenB extends StatefulWidget {
  Function func;
  ScreenB({Key key, @required this.func})
      : super(key: key);
  @override
  _ScreenBState createState() => _ScreenBState();
}

class _ScreenBState extends State<ScreenB> {
  _ScreenBState();

  var _controller = PageController();


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        actions: [
          IconButton(
            icon: Icon(Icons.access_alarm_sharp),
            onPressed: () async {
              widget.func(0);
            },
          ),
        ],
      ),
      body: PageView.custom(
          dragStartBehavior: DragStartBehavior.start,
          controller: _controller,
          physics: NeverScrollableScrollPhysics(),
          scrollDirection: Axis.horizontal,
          childrenDelegate: SliverChildBuilderDelegate((ctx, pageIndex) =>
              GestureDetector(
                  onPanUpdate: (details) async {
                    if (details.delta.dx < 0) {
                      _controller.nextPage(
                          duration: Duration(milliseconds: 200),
                          curve: Curves.easeInOut);

                      print('function called');
                    }
                  },
                  child: Center(
                      child: Container(
                          width: 200,
                          height: 200,
                          color: Colors.red,
                          child: Text('hi')))))),
    );
  }
}

Thanks in advance!


Solution

  • The problem is that you are using the onPanUpdate method, which is triggered every time a user drags their finger either right or left. You should use the onPanEnd method, which is only triggered when the user's finger is off the screen after dragging either left or right. The function below will work fine.

    onPanEnd: (details) async { if (details.velocity.pixelsPerSecond.dx < 0) { _controller.nextPage( duration: Duration(milliseconds: 200), curve: Curves.easeInOut); print('function called'); } }