8

I new to flutter and i have a counter button that i want to prevent it from multiple touch.

The Tap Function is defined under Inkwell component (onTap: () => counterBloc.doCount(context)).

if i run this apps and doing multi touch, counter will go up quickly, but i dont want it happen. any idea ?

below are my code :

  Expanded(
  child: Container(
    padding: EdgeInsets.only(right: 16),
    alignment: Alignment.centerRight,
    child: InkWell(
      onTap: () => counterBloc.doCount(context),
      child: Stack(
        alignment: Alignment.center,
        children: <Widget>[
          Image.asset("assets/images/home/tap.png", scale: 11,),
          StreamBuilder(
            initialData: 0,
            stream: counterBloc.counterStream,
            builder: (BuildContext ctx, AsyncSnapshot<int> snapshot){
              return Text("${snapshot.data}",style: TextStyle(color: Colors.white, fontSize: 120),);
            },
          ),
        ],
      )
    )
  )
)
questionasker
  • 2,536
  • 12
  • 55
  • 119

6 Answers6

10

you can use an AbsorbPointer

AbsorbPointer(
  absorbing: !enabled,
  child: InkWell(
    onTap: (){
      print('buttonClicked');
      setState(() {
        enabled = false;
      });
    },
    child: Container(
      width: 50.0,
      height: 50.0,
      color: Colors.red,
    ),
  ),
),

and when you want to enable the button again, set the enabled to true, don't forget to wrap it with a setState

Sami Kanafani
  • 14,244
  • 6
  • 45
  • 41
4

Try this? It should solve your problem.

class SafeOnTap extends StatefulWidget {
  SafeOnTap({
    Key? key,
    required this.child,
    required this.onSafeTap,
    this.intervalMs = 500,
  }) : super(key: key);
  final Widget child;
  final GestureTapCallback onSafeTap;
  final int intervalMs;

  @override
  _SafeOnTapState createState() => _SafeOnTapState();
}

class _SafeOnTapState extends State<SafeOnTap> {
  int lastTimeClicked = 0;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        final now = DateTime.now().millisecondsSinceEpoch;
        if (now - lastTimeClicked < widget.intervalMs) {
          return;
        }
        lastTimeClicked = now;
        widget.onSafeTap();
      },
      child: widget.child,
    );
  }
}

You can wrap any kind of widget if you want.

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Center(
          child: Column(
            children: [
              // every click need to wait for 500ms
              SafeOnTap(
                onSafeTap: () => log('500ms'),
                child: Container(
                  width: double.infinity,
                  height: 200,
                  child: Center(child: Text('500ms click me')),
                ),
              ),
              // every click need to wait for 2000ms
              SafeOnTap(
                intervalMs: 2000,
                onSafeTap: () => log('2000ms'),
                child: Container(
                  width: double.infinity,
                  height: 200,
                  child: Center(child: Text('2000ms click me')),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
loalexzzzz
  • 463
  • 1
  • 7
  • 16
0

Another option is to use debouncing to prevent this kind of behaviour ie with easy_debounce, or implementing your own debounce.

sabetai
  • 83
  • 2
  • 6
0

You can also use IgnorePointer

IgnorePointer(
ignoring: !isEnabled
child: yourChildWidget
)

And when you disable the component, it starts ignoring the touches within the boundary of the widget.

0

I personally wouldn't rely on setState, I'd go with a simple solution like this:

Widget createMultiClickPreventedButton(String text, VoidCallback clickHandler) {
  var clicked = false;
  return ElevatedButton(
  child: Text(text),
  onPressed: () {
    if (!clicked) {
      clicked = true;
      clickHandler.call();
    }
  });
}
Andras Kloczl
  • 8,415
  • 2
  • 21
  • 23
0

You can also use a Stream to make counter to count only on debounced taps.

final BehaviourSubject onTapStream = BehaviourSubject()

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

  // Debounce your taps here
  onTapStream.debounceTime(const Duration(milliseconds: 300)).listen((_) {
      
     // Do something on tap
     print(1);
  });
}