2

I have a GestureDetector that need to launch a url. But if the gesture gets multiple taps, then launch is called multiple times.

In the current code im trying to use a state _isButtonTapped to control the tap. But the .whenComplete is somehow call before the launch is preformed?

   _isButtonTapped = false

   Widget _buildButton(String key, Text title, String url) {
    _onTapped() async {
      if (await canLaunch(url)) {
        launch(url).whenComplete(
          () => setState(() {
                _isButtonTapped = false;
              }),
        );
      }
    }

    return GestureDetector(
      onTap: () {
        _isButtonTapped ? null : _onTapped();
        setState(() {
          _isButtonTapped = true;
        });
      },
      child: Container(
        child: Padding(
          padding: EdgeInsets.all(6.0),
          child: Center(child: title),
        ),
      ),
    );
  }

4 Answers4

3

Try this:

class _HomePageState extends State<HomePage> {
  bool _isButtonTapped = false;
  String _url = "https://google.ca";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          color: Colors.blue,
          width: 100,
          height: 100,
          child: GestureDetector(
            onTap: () async {
              if (!_isButtonTapped) { // only allow click if it is false
                _isButtonTapped = true; // make it true when clicked
                if (await canLaunch(_url)) {
                  await launch(_url);
                  _isButtonTapped = false; // once url is launched successfully, we again make it false, allowing tapping again
                }
              }
            },
          ),
        ),
      ),
    );
  }
}
CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
  • Thanks for the answer, but this does not change anything... It does work if '_isButtonTapped = false;' on the last line is removed. But then the button can only be clicked once permanently. Its like its the launch(_url) that is not awaited and the _isButtonTapped is set to false before the action is finished... I will try to look into that :) – Rune Alexander Larsen Jun 04 '19 at 11:14
  • 1
    far better solution – saurabh yadav Oct 21 '21 at 05:20
2

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

the easiest way is in inkWell widget put doubleTap: () {}, it will do nothing, when user click multiple time

0

You have a bug in your code.

You are setting _isButtonTapped to true everytime you press it. Correct you onTap function:

  return GestureDetector(
      onTap: () {
        if (_isButtonTapped == false){
          _onTapped();
          setState(() {
            _isButtonTapped = true;
          }); 
        },
      }
      //...

Regarding why the whenComplete is not beign called when you expected, that's another problem. I never used it but tacking a quick look into the docs (https://api.flutter.dev/flutter/scheduler/TickerFuture/whenComplete.html) show us that are multiple ways of achiving this, including wraping the function in an try block and use thr finally cloused as the whenCompleted. You should take a look at he docs and tried it out. Can't help more with that detail.

Hope it helps you.

MarcoFerreira
  • 195
  • 1
  • 14