Does anybody know how to enable/disable a Flutter ElevatedButton? I've reviewed the documentation but I can't see anything that is obvious.
class IcoButton extends StatelessWidget {
IcoButton(
{#required this.lbl,
#required this.col,
#required this.ico,
#required this.onPress});
final String lbl;
final FaIcon ico;
final MaterialColor col;
final Function onPress;
#override
Widget build(BuildContext context) {
return ElevatedButton.icon(
label: Text(lbl),
icon: ico,
style: ElevatedButton.styleFrom(
primary: col,
onPrimary: Colors.white,
minimumSize: Size(160.0, 60.0),
textStyle: TextStyle(
fontSize: 24,
),
),
onPressed: onPress,
);
}
}
Passing null to the onPressed callback will disable the button.
If onPressed and onLongPress callbacks are null, then the button will be disabled.
https://api.flutter.dev/flutter/material/ElevatedButton-class.html
I use a member variable "_isDisable" to enable button or not. Put below code in the build function to init the view:
ElevatedButton(
onPressed: _isDisable? null : callBackFunction,
child: Text("submit"),
style: ButtonStyle(),
);
when you want to disable button, call
setState(() {
_isDisable = true;
});
when you want to enable button, call
setState(() {
_isDisable = false;
});
As others have pointed out, setting the onPressed callback to null will deactivate the button for you.
Note however that it is the callback itself that must be null, not its return, so like this:
onPressed: null,
And not like this
onPressed: () => null,
onPressed: () {
if (isDisabled == true) { return; }
setState(() { int a = 10; });
}
The proper way to do it is by passing null to the onPress callback as #Shannon and #ASAD HAMEED have mentioned. However, the AbsorbPointer widget is also worth a look.
Related
im trying to submit form on Dialog and i have a DateTimePicker button and need to make a validation on it also before submitting , what i want to do is showing a text error in case no date picked by changing my own variable "isValid" to false but the UI is not updating , i have to close the dialog and reopen it to see the error text even though i wrapped my column with a StatefulBuilder
my dialog photo here
here is my code
StatefulBuilder(builder: (context, StateSetter setState) {
return isValid == false
? Column(
children: [
ElevatedButton(
onPressed: () {
DateTimePicker(context)
.then((value) => setState(() {
_appointmentDateTime = value;
}));
},
child: Text(getTimeDate())),
Text(
'error',
style: TextStyle(
color: Colors.red, fontSize: 10),
),
],
)
: Column(
children: [
ElevatedButton(
onPressed: () {
DateTimePicker(context)
.then((value) => setState(() {
_appointmentDateTime = value;
}));
},
child: Text(getTimeDate())),
],
);
})
Validating form + toggling the isValid Value is working fine
OutlinedButton(
onPressed: () async {
if (_formKey.currentState.validate() &&
_appointmentDateTime != null) {
String date = DateFormat('yyyy-MM-dd hh:mm')
.format(_appointmentDateTime);
var appointment = Appointment(
patientName: widget.patient.name,
date: date,
hospital: _hospitalController.text,
await FirebaseApi.addPatientAppointment(
widget.patient.id, appointment);
print('Appointment Created ');
_formKey.currentState.reset();
setState(() {
translator = null;
_appointmentDateTime = null;
});
Navigator.pop(context);
}
else {
setState(() {
isValid = !isValid;
});
}
},
child: Text('Add Appointment')),
It can get confusing when writing the code like this when dealing with Dialogs. The setState you are using in the OutlinedButton is not the same as the setState used in the StatefulBuilder. You need to enclose your OutlinedButton inside the StatefulBuilder too. If you ever use a StatefulBuilder inside a stateful widget, it is better to use a different name like e.g setDialogState.
It is even better to create a separate stateful widget class just for your Dialog contents and pass the formKey and anything else than using a StatefulBuilder in this case to avoid confusion.
import 'package:flutter/material.dart';
Widget justButton({
String btText = '',
Color bgColor = Colors.blue,
Color? txtColor = Colors.white,
Color borderColor = Colors.black,
void Function() onpressedAction,//here iam getting problem
}) {
return OutlinedButton(
//i wanted to refactor this onpressed
onPressed: () {
print('Go to events Page');
},
child: Text(btText),
style: OutlinedButton.styleFrom(
backgroundColor: bgColor,
primary: txtColor,
side: BorderSide(color: borderColor)),
);
}
This is my code here I have trying to refactor the onpressed outlinedButton ,
How can it possible to refactor a function
Did you want to fix error?
Here is my refactoring result.
import 'package:flutter/material.dart';
Widget justButton({
String btText = '',
Color bgColor = Colors.blue,
Color? txtColor = Colors.white,
Color borderColor = Colors.black,
Function? onpressedAction,//here iam getting problem
}) {
return OutlinedButton(
//i wanted to refactor this onpressed
onPressed: () => onpressedAction!(),
child: Text(btText),
style: OutlinedButton.styleFrom(
backgroundColor: bgColor,
primary: txtColor,
side: BorderSide(color: borderColor)),
);
}
The onpressed method accepts a VoidCallBack type, which is just a fancy way of saying void function(). Except, it doesn't contain any space, You can see for yourself here. So declare it like this.
Widget justButton({
....
VoidCallBack? onpressedAction,
}){
return OutlinedButton(
....
onPressed: onpressedAction,
....
}
create function like this
Widget customOutlinedButton(Function? func){
return OutlinedButton(
onPressed: func,
child: Text(btText),
style: OutlinedButton.styleFrom(
backgroundColor: bgColor,
primary: txtColor,
side: BorderSide(color: borderColor)),
);
}
now just pass the function you want to call in when onpressed
customOutlinedButton(*your function here*);
I have a widget test that taps an item in the DropdownButton. That should fire the onChanged callback but it doesn't. Here is the test code. The mock is Mockito.
void main() {
//Use a dummy instead of the fake. The fake does too much stuff
final mockServiceClient = MockTheServiceClient();
final apiClient = GrpcApiClient(client: mockServiceClient);
when(mockServiceClient.logEvent(any))
.thenAnswer((_) => MockResponseFuture(LogEventResponse()));
testWidgets("select asset type", (tester) async {
//sets the screen size
tester.binding.window.physicalSizeTestValue = const Size(3840, 2160);
// resets the screen to its orinal size after the test end
addTearDown(tester.binding.window.clearPhysicalSizeTestValue);
await tester.pumpWidget(AssetApp(apiClient), const Duration(seconds: 5));
//Construct key with '{DDLKey}_{Id}'
await tester
.tap(find.byKey(ValueKey("${assetTypeDropDownKey.value}_PUMP")));
await tester.pumpAndSettle(const Duration(seconds: 5));
verify(mockServiceClient.logEvent(any)).called(1);
});
}
This is the build method of the widget:
#override
Widget build(BuildContext context) {
return DropdownButton<DropDownItemDefinition>(
underline: Container(),
dropdownColor: Theme.of(context).cardColor,
hint: Text(
hintText,
style: Theme.of(context).textTheme.button,
),
//TODO: Use the theme here
icon: Icon(
Icons.arrow_drop_down,
color: Theme.of(context).dividerColor,
),
value: getValue(),
onChanged: (ddd) {
setState(() {
onValueChanged(ddd!);
});
},
items: itemss.map<DropdownMenuItem<DropDownItemDefinition>>((value) {
return DropdownMenuItem<DropDownItemDefinition>(
key: ValueKey(
"${(key is ValueKey) ? (key as ValueKey?)?.value.toString() :
''}_${value.id}"),
value: value,
child: Tooltip(
message: value.toolTipText,
child: Container(
margin: dropdownPadding,
child: Text(value.displayText,
style: Theme.of(context).textTheme.headline3))),
);
}).toList(),
);
}
Note that the onValueChanged function calls the logEvent call. The onChanged callback never happens and the test fails. This is the code it should fire.
Future onAssetTypeChange(DropDownItemDefinition newValue) async {
await assetApiClient.logChange(record.id, newValue, DateTime.now());
}
Why does the callback never fire?
Note: I made another widget test and the Mock does verify that the client was called correctly. I think there is some issue with the callback as part of the widget test.
You need to first instruct the driver to tap on the DropdownButton itself, and then, after the dropdown popup shows up, tap on the DropdownMenuItem.
The driver can't find a DropdownMenuItem from the dropdown if the dropdown itself is not active/painted on the screen.
I would like to know whether there is a way to avoid the color tweening that happens upon clicking on a RawMaterialButton and FlatButton. There is a slight animation between the default color and the highlight color. I want this color switch to happen instantaneously.
Sample Button:
child: RawMaterialButton(
onPressed: () {},
highlightColor: Colors.red,
splashColor: Colors.transparent,
fillColor: Colors.blue,
elevation: 0.0,
highlightElevation: 0.0,
animationDuration: Duration.zero,
focusColor: Colors.transparent,
),
Make your own custom button using a GestureDetector. There is likely no way of getting what you want with these button types without modifying the source.
I created a sample button that instantly changes between the default and highlight color and takes an onTap parameter as well so it can be used very similarly to the button types you're used to.
class CustButton extends StatefulWidget {
CustButton({this.onTap});
final VoidCallback onTap;
#override
_CustButtonState createState() => _CustButtonState();
}
class _CustButtonState extends State<CustButton> {
Color buttonColor = Colors.blue;
#override
Widget build(BuildContext context) {
return Container(
color: buttonColor,
child: GestureDetector(
onTapDown: (details) {
setState(() {
buttonColor = Colors.red;
});
widget.onTap();
},
onTapUp: (details) {
setState(() {
buttonColor = Colors.blue;
});
},
),
);
}
}
there's no named parameters to configure a single tap to trigger Tooltip,
my feeling about the default longPress interaction is that users cannot find this deep-buried function.
I tried to find some hint in tooltip source code but failed.
Tooltip(
message: 'this is something',
child: SizedBox(...),
)
First, define globalkey: GlobalKey _toolTipKey = GlobalKey();
Then wrap your tooltip:
GestureDetector(
onTap: () {
final dynamic _toolTip = _toolTipKey.currentState;
_toolTip.ensureTooltipVisible();
},
child: Tooltip(
key: _toolTipKey,
message: "Your message",
child: Icon(
Icons.info,
),
),
),
Easiest way is to use:
triggerMode: TooltipTriggerMode.tap
Here's an example:
Tooltip(
triggerMode: TooltipTriggerMode.tap,
message: 'this is something',
child: SizedBox(...),
)
The easiest way to get a functionality you need is to clone the original Tooltip widget (call it e.g. TooltipCustom) and change inner GestureDetector behavior.
Particularly replace onLongPress to onTap:
class TooltipCustom extends StatefulWidget {
/// Creates a tooltip.
...
#override
Widget build(BuildContext context) {
...
Widget result = GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: _handleLongPress,
excludeFromSemantics: true,
child: Semantics(
label: excludeFromSemantics ? null : widget.message,
child: widget.child,
),
);
...
return result;
}
}
P.S. It's possible to lose a tooltip hiding feature. Take a look at _handlePointerEvent(PointerEvent event) handler function and realize a proper call of _hideTooltip() method.