321

I'm just starting to get the hang of Flutter, but I'm having trouble figuring out how to set the enabled state of a button.

From the docs, it says to set onPressed to null to disable a button, and give it a value to enable it. This is fine if the button continues to be in the same state for the lifecycle.

I get the impression I need to create a custom Stateful widget that will allow me to update the button's enabled state (or onPressed callback) somehow.

So my question is how would I do that? This seems like a pretty straightforward requirement, but I can't find anything in the docs on how to do it.

Thanks.

chris84948
  • 3,940
  • 3
  • 22
  • 25

17 Answers17

307

According to the docs:

If the onPressed callback is null, then the button will be disabled and by default will resemble a flat button in the disabledColor.

So, you might do something like this:

RaisedButton(
  onPressed: calculateWhetherDisabledReturnsBool() ? null : () => whatToDoOnPressed,
  child: Text('Button text')
);
reza
  • 1,354
  • 1
  • 7
  • 25
Steve Alexander
  • 3,327
  • 1
  • 10
  • 6
  • 9
    Judging from the docs, this is how it's ment to be implemented. With the accepted answer properties like `disabledElevation`, `disabledColor` and `DisabledTextColor` won't work as intended. – Joel Broström Feb 11 '19 at 10:55
  • 3
    Pff thanks for this Steve, was not planning on going through all the code of the currently accepted answer. @chris84948, consider changing this to the accepted answer. – CularBytes Mar 08 '20 at 12:49
  • yes, but can you destroy the house from inside and still run away? or can you null the onPress from setStatus within the onPress? – none Oct 27 '20 at 11:31
  • 3
    Although, after null safety we can not use 'null' so you can use `onPressed: _isLoading ? () => {} : () => _signImWithGoogle(context),` – Kru Feb 05 '22 at 18:49
  • Here in 2023, [ElevatedButton](https://api.flutter.dev/flutter/material/ElevatedButton-class.html) is the new widget. Use this docs link since the one in the answer 404s. "If onPressed and onLongPress callbacks are null, then the button will be disabled." – Ross Llewallyn Apr 06 '23 at 15:37
258

I think you may want to introduce some helper functions to build your button as well as a Stateful widget along with some property to key off of.

  • Use a StatefulWidget/State and create a variable to hold your condition (e.g. isButtonDisabled)
  • Set this to true initially (if that's what you desire)
  • When rendering the button, don't directly set the onPressed value to either null or some function onPressed: () {}
  • Instead, conditionally set it using a ternary or a helper function (example below)
  • Check the isButtonDisabled as part of this conditional and return either null or some function.
  • When the button is pressed (or whenever you want to disable the button) use setState(() => isButtonDisabled = true) to flip the conditional variable.
  • Flutter will call the build() method again with the new state and the button will be rendered with a null press handler and be disabled.

Here's is some more context using the Flutter counter project.

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  bool _isButtonDisabled;

  @override
  void initState() {
    _isButtonDisabled = false;
  }

  void _incrementCounter() {
    setState(() {
      _isButtonDisabled = true;
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("The App"),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              'You have pushed the button this many times:',
            ),
            new Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
            _buildCounterButton(),
          ],
        ),
      ),
    );
  }

  Widget _buildCounterButton() {
    return new RaisedButton(
      child: new Text(
        _isButtonDisabled ? "Hold on..." : "Increment"
      ),
      onPressed: _isButtonDisabled ? null : _incrementCounter,
    );
  }
}

In this example I am using an inline ternary to conditionally set the Text and onPressed, but it may be more appropriate for you to extract this into a function (you can use this same method to change the text of the button as well):

Widget _buildCounterButton() {
    return new RaisedButton(
      child: new Text(
        _isButtonDisabled ? "Hold on..." : "Increment"
      ),
      onPressed: _counterButtonPress(),
    );
  }

  Function _counterButtonPress() {
    if (_isButtonDisabled) {
      return null;
    } else {
      return () {
        // do anything else you may want to here
        _incrementCounter();
      };
    }
  }
Ashton Thomas
  • 16,659
  • 5
  • 31
  • 25
  • 6
    You need to add fat arrow function as an argument, otherwise the _incrementCounter() function will be called right away when button becomes enabled. This way it will actually wait until the button is clicked: The onPressed should look like this: `onPressed: _isButtonDisabled ? null : () => _incrementCounter` – Vit Veres Apr 12 '18 at 05:21
  • 3
    @vitVeres that is usually true but the _counterButtonPress() is returning a function `return () {}` so this is intentional. I don't want to use the fat arrow here as I want the function to execute and return `null` and disable the button. – Ashton Thomas Apr 14 '18 at 14:34
  • @AshtonThomas Yeah, in the extracted method _counterButtonPress() it is exactly as you explained, but I was referencing to the code with ternary operator before you suggested the extraction. In your first example it will cause the execution of _incrementCounter() method when button should be enabled. Next time I'll try to point out what I mean more precisely :) – Vit Veres Apr 15 '18 at 15:55
  • 202
    What was wrong with using a ```disabled``` property, Flutter team? This is just not intuitive :-/ – SoftWyer Aug 10 '19 at 16:19
  • 1
    the correct way is with AbsorbPointer or IgnorePointer. Simply widget way instead of logic with setting up onPressed to null. – ejdrian313 Nov 18 '19 at 13:16
  • 2
    This is not good enough because it assumes the onPress: is synchronous code while it is very possible the code is asynchronous and operational, splitting every button code to multiple function to work around this causes a maintenance nightmare – Raiden Core Jul 02 '20 at 20:57
112

The simple answer is onPressed : null gives a disabled button.

BINAY THAPA MAGAR
  • 4,017
  • 4
  • 16
  • 24
70

Disables click:

onPressed: null

Enables click:

onPressed: () => fooFunction() 
// or
onPressed: fooFunction

Combination:

onPressed: shouldEnable ? fooFunction : null
CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
31

For a specific and limited number of widgets, wrapping them in a widget IgnorePointer does exactly this: when its ignoring property is set to true, the sub-widget (actually, the entire subtree) is not clickable.

IgnorePointer(
    ignoring: true, // or false
    child: RaisedButton(
        onPressed: _logInWithFacebook,
        child: Text("Facebook sign-in"),
        ),
),

Otherwise, if you intend to disable an entire subtree, look into AbsorbPointer().

edmond
  • 1,432
  • 2
  • 15
  • 19
21

This is the easiest way in my opinion:

RaisedButton(
  child: Text("PRESS BUTTON"),
  onPressed: booleanCondition
    ? () => myTapCallback()
    : null
)
MatPag
  • 41,742
  • 14
  • 105
  • 114
17

Enable and Disable functionality is same for most of the widgets.

Ex, button , switch, checkbox etc.

Just set the onPressed property as shown below

onPressed : null returns Disabled widget

onPressed : (){} or onPressed : _functionName returns Enabled widget

Vicky Salunkhe
  • 9,869
  • 6
  • 42
  • 59
16

You can also use the AbsorbPointer, and you can use it in the following way:

AbsorbPointer(
      absorbing: true, // by default is true
      child: RaisedButton(
        onPressed: (){
          print('pending to implement onPressed function');
        },
        child: Text("Button Click!!!"),
      ),
    ),

If you want to know more about this widget, you can check the following link Flutter Docs

Juanes30
  • 2,398
  • 2
  • 24
  • 38
16

This answer is based on updated Buttons TextButton/ElevatedButton/OutlinedButton for Flutter 2.x

Still, buttons are enabled or disabled based on onPressed property. If that property is null then button would be disabled. If you will assign function to onPressed then button would be enabled. In the below snippets, I have shown how to enable/disable button and update it's style accordingly.

This post also indicating that how to apply different styles to new Flutter 2.x buttons.

enter image description here

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      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> {
  bool textBtnswitchState = true;
  bool elevatedBtnSwitchState = true;
  bool outlinedBtnState = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: <Widget>[
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                TextButton(
                  child: Text('Text Button'),
                  onPressed: textBtnswitchState ? () {} : null,
                  style: ButtonStyle(
                    foregroundColor: MaterialStateProperty.resolveWith(
                      (states) {
                        if (states.contains(MaterialState.disabled)) {
                          return Colors.grey;
                        } else {
                          return Colors.red;
                        }
                      },
                    ),
                  ),
                ),
                Column(
                  children: [
                    Text('Change State'),
                    Switch(
                      value: textBtnswitchState,
                      onChanged: (newState) {
                        setState(() {
                          textBtnswitchState = !textBtnswitchState;
                        });
                      },
                    ),
                  ],
                )
              ],
            ),
            Divider(),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                ElevatedButton(
                  child: Text('Text Button'),
                  onPressed: elevatedBtnSwitchState ? () {} : null,
                  style: ButtonStyle(
                    foregroundColor: MaterialStateProperty.resolveWith(
                      (states) {
                        if (states.contains(MaterialState.disabled)) {
                          return Colors.grey;
                        } else {
                          return Colors.white;
                        }
                      },
                    ),
                  ),
                ),
                Column(
                  children: [
                    Text('Change State'),
                    Switch(
                      value: elevatedBtnSwitchState,
                      onChanged: (newState) {
                        setState(() {
                          elevatedBtnSwitchState = !elevatedBtnSwitchState;
                        });
                      },
                    ),
                  ],
                )
              ],
            ),
            Divider(),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                OutlinedButton(
                  child: Text('Outlined Button'),
                  onPressed: outlinedBtnState ? () {} : null,
                  style: ButtonStyle(
                      foregroundColor: MaterialStateProperty.resolveWith(
                    (states) {
                      if (states.contains(MaterialState.disabled)) {
                        return Colors.grey;
                      } else {
                        return Colors.red;
                      }
                    },
                  ), side: MaterialStateProperty.resolveWith((states) {
                    if (states.contains(MaterialState.disabled)) {
                      return BorderSide(color: Colors.grey);
                    } else {
                      return BorderSide(color: Colors.red);
                    }
                  })),
                ),
                Column(
                  children: [
                    Text('Change State'),
                    Switch(
                      value: outlinedBtnState,
                      onChanged: (newState) {
                        setState(() {
                          outlinedBtnState = !outlinedBtnState;
                        });
                      },
                    ),
                  ],
                )
              ],
            ),
          ],
        ),
      ),
    );
  }
}
Iducool
  • 3,543
  • 2
  • 24
  • 45
10

For disabling any Button in flutter such as FlatButton, RaisedButton, MaterialButton, IconButton etc all you need to do is to set the onPressed and onLongPress properties to null. Below is some simple examples for some of the buttons:

FlatButton (Enabled)

FlatButton(
  onPressed: (){}, 
  onLongPress: null, // Set one as NOT null is enough to enable the button
  textColor: Colors.black,
  disabledColor: Colors.orange,
  disabledTextColor: Colors.white,
  child: Text('Flat Button'),
),

enter image description here enter image description here

FlatButton (Disabled)

FlatButton(
  onPressed: null,
  onLongPress: null,
  textColor: Colors.black,
  disabledColor: Colors.orange,
  disabledTextColor: Colors.white,
  child: Text('Flat Button'),
),

enter image description here

RaisedButton (Enabled)

RaisedButton(
  onPressed: (){},
  onLongPress: null, // Set one as NOT null is enough to enable the button
  // For when the button is enabled
  color: Colors.lightBlueAccent,
  textColor: Colors.black,
  splashColor: Colors.blue,
  elevation: 8.0,

  // For when the button is disabled
  disabledTextColor: Colors.white,
  disabledColor: Colors.orange,
  disabledElevation: 0.0,

  child: Text('Raised Button'),
),

enter image description here

RaisedButton (Disabled)

RaisedButton(
  onPressed: null,
  onLongPress: null,
  // For when the button is enabled
  color: Colors.lightBlueAccent,
  textColor: Colors.black,
  splashColor: Colors.blue,
  elevation: 8.0,

  // For when the button is disabled
  disabledTextColor: Colors.white,
  disabledColor: Colors.orange,
  disabledElevation: 0.0,

  child: Text('Raised Button'),
),

enter image description here

IconButton (Enabled)

IconButton(
  onPressed: () {},
  icon: Icon(Icons.card_giftcard_rounded),
  color: Colors.lightBlueAccent,
            
  disabledColor: Colors.orange,
),

enter image description here enter image description here

IconButton (Disabled)

IconButton(
  onPressed: null,
  icon: Icon(Icons.card_giftcard_rounded),
  color: Colors.lightBlueAccent,
            
  disabledColor: Colors.orange,
),

enter image description here

Note: Some of buttons such as IconButton have only the onPressed property.

Taba
  • 3,850
  • 4
  • 36
  • 51
5

This is the easiest way to disable a button in Flutter is assign the null value to the onPressed

ElevatedButton(
  style: ElevatedButton.styleFrom(
    primary: Colors.blue, // background
    onPrimary: Colors.white, // foreground
  ),
  onPressed: null,
  child: Text('ElevatedButton'),
),
Fakhriddin Abdullaev
  • 4,169
  • 2
  • 35
  • 37
4

There are two ways of doing this:

1- https://stackoverflow.com/a/49354576/5499531

2- You can use a MaterialStatesController:

final _statesController = MaterialStatesController();

and then change the state to:

_statesController.update(
   MaterialState.disabled,
   true, // or false depending on your logic
);

On your button

ElevatedButton(
    onPressed: _onPressed,
    statesController: _statesController,
    child: Text("Awesome"),
),

In addition you can change the button style when is disable: in your theme setup:

....
elevatedButtonTheme: ElevatedButtonThemeData(
              style: ElevatedButton.styleFrom(
                backgroundColor: colors.primary500, // set your own color
                textStyle: button, // set your own style
                onPrimary: colors.onPrimary100, // set your own color
                enableFeedback: true,
                disabledBackgroundColor: colors.primary300, // set your own color
                disabledForegroundColor: colors.primary300, // set your own color
                disabledMouseCursor: SystemMouseCursors.forbidden, // when is disable the change the cursor type
              ),
            ),
...
Amaury Ricardo
  • 267
  • 2
  • 5
2

You can use this code in your app for button with loading and disable:

class BtnPrimary extends StatelessWidget {
  bool loading;
  String label;
  VoidCallback onPressed;

  BtnPrimary(
      {required this.label, required this.onPressed, this.loading = false});

  @override
  Widget build(BuildContext context) {
    return ElevatedButton.icon(
      icon: loading
          ? const SizedBox(
              child: CircularProgressIndicator(
                color: Colors.white,
              ),
              width: 20,
              height: 20)
          : const SizedBox(width: 0, height: 0),
      label: loading ? const Text('Waiting...'): Text(label),
      onPressed: loading ? null : onPressed,
    );
  }
}

I hope useful

Ehsan Kalali
  • 411
  • 6
  • 16
0

You can set also blank condition, in place of set null

         var isDisable=true;

   

          RaisedButton(
              padding: const EdgeInsets.all(20),
              textColor: Colors.white,
              color: Colors.green,
              onPressed:  isDisable
                  ? () => (){} : myClickingData(),
              child: Text('Button'),
            )
Shirsh Shukla
  • 5,491
  • 3
  • 31
  • 44
0

see below of the possible solution, add a 'ValueListenableBuilder' of 'TextEditingValue' listening to controller (TextEditingController) and return your function call if controller.text is not empty and return 'null' if it is empty.

// valuelistenablebuilder wraped around button

  ValueListenableBuilder<TextEditingValue>(
                  valueListenable: textFieldController,
                  builder: (context, ctrl, __) => ElevatedButton(                    
                    onPressed: ctrl.text.isNotEmpty ? yourFunctionCall : null,
                    child: Text(
                      'SUBMIT',
                      style: GoogleFonts.roboto(fontSize: 20.0),
                    ),
                  ),
                ),

//texfield

 TextField(controller: textFieldController,
                 onChanged: (newValue) {
                  textFieldText = newValue;
                },
              ),

builder will listen to controller and enables button only when textfield is in use. I hope this answers the question. let me know..

-2

I like to use flutter_mobx for this and work on the state.

Next I use an observer:

Container(child: Observer(builder: (_) {
  var method;
  if (!controller.isDisabledButton) method = controller.methodController;
  return RaiseButton(child: Text('Test') onPressed: method);
}));

On the Controller:

@observable
bool isDisabledButton = true;

Then inside the control you can manipulate this variable as you want.

Refs.: Flutter mobx

-6

If you are searching for a quick way and don't care about letting the user actually clicking more then once on a button. You could do it also the following way:

// Constant whether button is clicked
bool isClicked = false;

and then checking in the onPressed() function whether the user has already clicked the button or not.

onPressed: () async {
    if (!isClicked) {
       isClicked = true;
       // await Your normal function
    } else {
       Toast.show(
          "You click already on this button", context,
          duration: Toast.LENGTH_LONG, gravity: Toast.BOTTOM);
    }
}