Reputation:
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);
}
Upvotes: 18
Views: 46613
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
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
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
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
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
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
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
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
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
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
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