18

Clicked Button multiple times same time, open pages multiple times. How to fix this issue? I also uploaded the gif file on my application(double click on the image).

     Container(
                            padding: EdgeInsets.all(10.0),
                            child: ButtonTheme(
                              minWidth: 10.0,
                              height: 40.0,
                              child: RaisedButton(
                                child: Text(
                                  AppTranslations.of(context)
                                      .text("loginpage_button"),
                                  style: TextStyle(
                                      color: Colors.white, fontSize: 15.0),
                                ),
                                onPressed: () async{
                                  (isOffline)
                                    ? _showSnackBar()
                                      : checking2(usernameController, context, _url);
                                },
                                color: Colors.blue,
                                padding: EdgeInsets.all(20.0),
                              ),
                            ),
margin: EdgeInsets.only(top: 0.0),

)

I used this code, it's working, but user types username incorrectly, user cant click button second type. this is my code.

onPressed: () async {
 if (_firstClick) {
_firstClick = false;
(isOffline)
? _showSnackBar()
: checking2(usernameController, context, _url);
}

enter image description here

CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440

10 Answers10

14

Solved this in my application based on calculating time difference.

First, declare a DateTime variable and define the function as follows:

DateTime loginClickTime;

bool isRedundentClick(DateTime currentTime) {
  if (loginClickTime == null) {
    loginClickTime = currentTime;
    print("first click");
    return false;
  }
  print('diff is ${currentTime.difference(loginClickTime).inSeconds}');
  if (currentTime.difference(loginClickTime).inSeconds < 10) {
    // set this difference time in seconds
    return true;
  }

  loginClickTime = currentTime;
  return false;
}

In the login button call the function as follows to check for redundancy:

RaisedButton(
  child: Text('Login'),
  onPressed: () {
    if (isRedundentClick(DateTime.now())) {
      print('hold on, processing');
      return;
    }
    print('run process');
  },
),
My Car
  • 4,198
  • 5
  • 17
  • 50
PreciseSpeech
  • 317
  • 4
  • 9
11

Create a bool variable which will be true when the button is pressed, (hence, initial value is set to false).

bool _clicked = false; 
  
@override
Widget build(BuildContext context) {
  return Scaffold(
    body: RaisedButton(
      child: Text('Button'),
      onPressed: _clicked
        ? null
        : () {
            setState(() => _clicked = true); // set it to true now
          },
    ),
  );
}
My Car
  • 4,198
  • 5
  • 17
  • 50
CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
7

You can use a bool variable to save the state of your RaisedButton:

First create the variable a set its initial value :

var _firstPress = true;

Then add _firstPress inside your onPressed function :

Container(
  padding: EdgeInsets.all(10.0),
  child: ButtonTheme(
    minWidth: 10.0,
    height: 40.0,
    child: RaisedButton(
      child: Text(
        AppTranslations.of(context).text("loginpage_button"),
        style: TextStyle(color: Colors.white, fontSize: 15.0),
      ),
      onPressed: () async {
        // This is what you should add in your code
        if (_firstPress) {
          _firstPress = false;
          (isOffline) ? _showSnackBar() : checking2(usernameController, context, _url);
        }
      },
      color: Colors.blue,
      padding: EdgeInsets.all(20.0),
    ),
  ),
  margin: EdgeInsets.only(top: 0.0),
),

This way your onPressed function will only respond to the RaisedButton's first click.

My Car
  • 4,198
  • 5
  • 17
  • 50
Mazin Ibrahim
  • 7,433
  • 2
  • 33
  • 40
3

Disabling multiple click events in a flutter with StatelessWidget. Using as a shareable widget.

Simple example:

class SingleTapEvent extends StatelessWidget {
  final Widget child;
  final Function() onTap;

  bool singleTap = false;

  SingleTapEvent(
      {Key? key, required this.child, required this.onTap, singleTap = false})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return InkWell(
         onTap: ()  {
          if (!singleTap) {
            Function.apply(onTap, []);
            singleTap = true;
            Future.delayed(const Duration(seconds: 3)).then((value) => singleTap = false);
          }
        },
        child: child);
  }
}

Usage:

    SingleTapEvent(
      onTap: () {
        print("Clicked");
      },
      child: Text("Click me"),
    );
Techalgoware
  • 540
  • 6
  • 11
1

I've written two classes for myself that may be helpful for others. They encapsulate the answer given by others in this thread so that you don't have a bunch of bools and assignment statements floating everywhere.

You pass your function to the class, and use the class' "invoke" method in place of the function. This currently does not support functions that need parameters, but is useful for the void case.

typedef void CallOnceFunction();
class CallOnce {
  bool _inFunction = false;

  final CallOnceFunction function;

  CallOnce(CallOnceFunction function) :
    assert(function != null),
    function = function
  ;

  void invoke() {
    if (_inFunction)
      return;

    _inFunction = true;
    function();
    _inFunction = false;
  }
}

typedef Future<void> CallOnceFuture();
class CallFutureOnce {
  bool _inFunction = false;

  final CallOnceFuture future;

  CallFutureOnce(CallOnceFuture future) :
    assert(future != null),
    future = future
  ;

  Future<void> invoke() async {
    if (_inFunction)
      return;

    _inFunction = true;
    await this.future();
    _inFunction = false;
  }
}

Update: Here's an example of both of these classes in action

/*Example*/
import 'package:flutter/material.dart';

class MyWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return new MyWidgetState();
  }
}

class MyWidgetState extends State<MyWidget> {

  CallOnce _callOnce;
  CallFutureOnce _callFutureOnce;

  void myFunction() {
    /*Custom Code*/
  }

  Future<void> myFutureFunction() async {
    /*Custom Code*/
    //await something()
  }

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

    this._callOnce = CallOnce(this.myFunction);
    this._callFutureOnce = CallFutureOnce(this.myFutureFunction);
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold (
      body: Center (
        child: RaisedButton (
          child: Text('Try Me'),
          onPressed: this._callOnce.invoke,
        ),
      ),
      floatingActionButton: FloatingActionButton (
        child: Icon(Icons.save),
        onPressed: this._callFutureOnce.invoke,
      ),
    );
  }
}
Eric D
  • 11
  • 2
1

Some of the other solutions do not work for me, and some of them are not isolated in their own state and I implemented my solution to encapsulate the functionality in my custom widget. I implemented it for IconButton but you could modify it with any tappable widget. Cheers:

import 'package:flutter/material.dart';

class AppIconButton extends StatefulWidget {
  const AppIconButton({
    Key? key,
    required this.onPressed,
    required this.icon,
    this.disableAfterClick = const Duration(milliseconds: 500),
  }) : super(key: key);

  final Function onPressed;
  final Widget icon;
  final Duration disableAfterClick;

  @override
  State<AppIconButton> createState() => _AppIconButtonState();
}

class _AppIconButtonState extends State<AppIconButton> {
  bool _acceptsClicks = true;

  @override
  Widget build(BuildContext context) {
    return IconButton(
      onPressed: () {
        if (_acceptsClicks) {
          //if you want to disable the button
          //use the variable with setState method
          //but it's not my case
          _acceptsClicks = false;
          widget.onPressed();
          Future.delayed(widget.disableAfterClick, () {
            if (mounted) {
              _acceptsClicks = true;
            }
          });
        }
        // else {
        //   debugPrint("Click ignored");
        // }
      },
      icon: widget.icon,
    );
  }
}
MeLean
  • 3,092
  • 6
  • 29
  • 43
0

This question is answered here How do I disable a Button in Flutter?

All you need to use statefulWidget and create a variable to hold your condition, And change it according to your event. Your button will be enable or disable according to your variable's value.

Suppose initial state of your variable, isDisable = false,that means - your button is enable by default. And after first clicking change the value of your state variable isDisable = true.

Robin
  • 4,902
  • 2
  • 27
  • 43
  • if the user enters username incorrectly the first time, user cant press button second time? –  Mar 21 '19 at 04:22
  • @BhanukaIsuru then render your button with some condition like this - if isDisalbe = false and username or password isn't correct, button will be enable. Actually if the username and password is correct, user should go another page, then you needn't show the login page anymore. I suggest you write your use cases, and put condition according to it. I think that will be helpful. – Robin Mar 21 '19 at 04:27
0

Instead of using RaisedButton directly, you can turn it into a StatefulWidget. Then use the ChangeNotifier to change it state from enable to disable and control button press function.It will also help you to reuse it in different places. Here is an example how can you do that

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  final ValueNotifier<MyButtonState> _myButtonStateChangeNotifier =
  ValueNotifier(MyButtonState.enable);

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: MyButton(
          buttonStateChangeNotifier: _myButtonStateChangeNotifier,
          onPressed: _onButtonPressed,
          text: "Click Me",
        ),
      ),
    );
  }

  _onButtonPressed() {
    print("Button Pressed");
    _myButtonStateChangeNotifier.value = MyButtonState.disable;
  }

}

enum MyButtonState {enable, disable}

class MyButton extends StatefulWidget {
  final VoidCallback onPressed;
  final String text;
  final TextStyle textStyle;
  final ValueNotifier<MyButtonState> buttonStateChangeNotifier;

  MyButton({
    @required this.onPressed,
    this.text = "",
    this.textStyle,
    this.buttonStateChangeNotifier,
  });

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

class _MyButtonState extends State<MyButton> {
  MyButtonState _myButtonState = MyButtonState.enable;
  @override
  void initState() {
    super.initState();
    if (widget.buttonStateChangeNotifier != null) {
      widget.buttonStateChangeNotifier.addListener(_handleButtonStateChange);
      _myButtonState = widget.buttonStateChangeNotifier.value;
    }
  }

  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.all(Radius.circular(4)),
      ),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text(widget.text)
        ],
      ),
      onPressed: _myButtonState == MyButtonState.enable
          ? _handleOnPress
          : null,
    );
  }

  _handleButtonStateChange() {
    setState(() {
      _myButtonState = widget.buttonStateChangeNotifier.value;
    });
  }

  _handleOnPress() {
    if (_myButtonState == MyButtonState.enable) {
      widget.onPressed();
    }
  }
}
Zakir
  • 1,305
  • 1
  • 10
  • 13
0

Thanks to @Mazin Ibrahim's suggestion above, setting a basic bool toggle flag works fine.

This implementation is based on handling the enable/disable logic at the callback level, independent of the widget layout details.

bool _isButtonEnabled = true;

MessageSql _messageSql = new MessageSql(); // DB helper class

final TextEditingController eCtrl = new TextEditingController();

_onSendMessage(String message) {

  if (! _isButtonEnabled) {
    return;
  }

  _isButtonEnabled = false;

  _messageSql.insert(message).then((resultId) {
    //  only update all if save is successful
    eCtrl.clear();

    AppUi.dismissKeyboard();

    _isButtonEnabled = true;

    Future.delayed(const Duration(milliseconds: 400), () {
      setState(() {});
    })
    .catchError((error, stackTrace) {
      print("outer: $error");
    });
}
Gene Bo
  • 11,284
  • 8
  • 90
  • 137
0

Similar to what PreciseSpeech and Sharman implemented, I made a few changes and it works.

DateTime loginClickTime = DateTime.now();

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





bool isRedundentClick(DateTime currentTime) {
    if (loginClickTime == '') {
      loginClickTime = currentTime;
      print("first click");
      return false;
    }
    print('diff is ${currentTime.difference(loginClickTime).inSeconds}');
    if (currentTime.difference(loginClickTime).inSeconds < 10) {
      //set this difference time in seconds
      return true;
    }

    loginClickTime = currentTime;
    return false;
}

Then in the OnPressed function

MaterialButton(
    child:Text('Login'),
    onPressed: (){
      if(isRedundentClick(DateTime.now())){
        print('hold on, processing');
        return;
      }
      print('run process');
      },
  )
   
A.B David
  • 11
  • 3