Repository: flutter
Issue: Explicitly expose "enabled" property in RaisedButton, MaterialButton and related classes
Labels: framework,f: material design,c: proposal,team-design,triaged-design

Problem:
1. It's my personal interpretation that Flutter has a principle of **explicit configuration over convention** (vs Ruby on Rails that is convention over configuration). Configuration makes for more verbose work products but there is less "magic" you have to figure out. 
1. Currently for `RaisedButton` and clan, there's no way to functionally set apart enabled state because it relies on callbacks themselves being null as a convention. 
1. My personal experience is that past frameworks over the last 25 years or so, I've used have consistently made enable/disable toggle an intrinsic, explicitly-declared feature of a button. I'd like to align Flutter with this prevailing convention. 

Example code as of Flutter 1.17: this is how I understand you need to handle active/inactive state. Note that the very presence of the onPressed callback itself needs to be null, making it impossible to refactor business rules elsewhere. Specifically the ternary operator is unavoidable, AFAICT:

```
bool _shouldEnable() => (fState == FState.pageUrlReady);

onPressed:
  _shouldEnable() ? () {_handleButtonPress()} : null, 
```

## Proposal
1. A new optional constructor parameter with backwards compatibility called `enable`: it specifies whether to render the widget enabled or disabled 
1. change to semantics of `onPressed` and `onLongPress` that also respects existing code for some deprecation window. 

```
/// NEW functionality: if enable named parameter is present, 
/// use that to determine whether the button should be enabled or not

...
onPressed: () {_handleButtonPress();},
enable: () => _shouldEnable(),
…

/// Legacy support example: if enable is NOT present, function as before
/// 
onPressed: 
  _shouldEnable() ? () {_handleButtonPress()} : null,
```

### Semantics

1. Backward compatibility: null behaviour still happens so this does not break any existing code. I would like to think though that there's a deprecation window for fully moving to explicit enable feature, meaning code that assumes that having those handlers as null would need to instead set the enable flag.
1. Definition of the `bool get enabled` method changed as below


### Questions
* Booleans have a habit of turning into enums. Should this be instead `presentationState` instead of enable or something, being (enabled, disabled, default) etc? 
* If there's no callback set do we also need to set a debug-level warning that the callback is null for an enabled button? See tests
* How do we best warn people of this new feature and encourage them to rely on `enabled` rather than implicitly via the null checks?

### Other parts of Flutter affected
* Impact of architecture across other elements: This "disable if null" appears to be consistently implemented across all / most UI widgets (see [onChanged for checkboxes](https://stackoverflow.com/questions/52174090/how-to-disable-checkbox-flutter) for example), so I think this semantic change should be done in a consistent manner across all widgets. It may well be better to deliver `RaisedButton.enabled` as an experiment first and see how people find it. 
* A bunch of downstream constructors may need to support this additional parameter e.g. outline_button, icon_button, flat_button, dropdown_button etc. 

## Preliminary analysis
Even adding as an optional attribute affects the surface area of a good chunk of classes and their corresponding unit tests. 

`RaisedButton` delegates most of its functionality to `MaterialButton`. Digging into the code: 

```
/// code as of Flutter 1.17: enabled is a calculated property of MaterialButton. 
bool get enabled => onPressed != null || onLongPress != null;
```

In this proposal, this becomes something like the code below: 
* Existing semantics are preserved with enable=true by default
* if user explicitly passes `enabled:false` and there's a non-null function for handling `onPress` or `onLongPress`, it will be disabled (currently not possible). 

```
final bool _enabled;
...
bool get enabled => (onPressed == null && onLongPress == null) ? false : _enabled;
```

### Constructor
enabled defaults to true. 

```
// constructor change
const RaisedButton({
…
@required VoidCallback onPressed,
VoidCallback onLongPress,
bool enabled=true,
…
```
### Tests
1. onPress and onLongPress null AND enabled=false -> ok --- new functionality
1. onPress or onLongPress not null and enabled=false --> ok -- new functionality
1. onPress and onLongPress null AND enabled=true -> ok, no change from current behaviour (will actually be disabled)
1. onPress or onLongPress not null and enabled=true --> ok, no change from current behaviour

Point (3) in the transition will be confusing, but these classes are immutable so it's unclear an easier way to make the transition to no future reliance on the callback presence / absence as a decider for the button active state.