user11065582
user11065582

Reputation:

How to disable a button after first click in Flutter?

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

Upvotes: 18

Views: 46613

Answers (11)

ramzieus
ramzieus

Reputation: 157

you can use this package guarded_button

A button with Guard displays CircularProgressIndicator until the moment async callback ends or minimum time passes. The minimum time can be set in Guard constructor. At the moment package works with Elevated, Outlined and Text buttons.

Upvotes: 0

CopsOnRoad
CopsOnRoad

Reputation: 268464

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
          },
    ),
  );
}

Upvotes: 13

Mazin Ibrahim
Mazin Ibrahim

Reputation: 7889

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.

Upvotes: 8

PreciseSpeech
PreciseSpeech

Reputation: 347

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');
  },
),

Upvotes: 16

Techalgoware
Techalgoware

Reputation: 620

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"),
    );

Upvotes: 3

A.B David
A.B David

Reputation: 11

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');
      },
  )
   

Upvotes: 0

MeLean
MeLean

Reputation: 3451

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,
    );
  }
}

Upvotes: 1

Eric D
Eric D

Reputation: 11

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,
      ),
    );
  }
}

Upvotes: 1

Gene Bo
Gene Bo

Reputation: 12103

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");
    });
}

Upvotes: 0

Zakir
Zakir

Reputation: 1565

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();
    }
  }
}

Upvotes: 0

Robin
Robin

Reputation: 5427

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.

Upvotes: 0

Related Questions