Repository: flutter
Issue: Better error description and better docs for `State.dispose`
Labels: framework,d: api docs,a: error message,P3,team-framework,triaged-framework

### Use case

I've stumbled upon #64935 today. 

<details>

<summary> Here is a reproducible code </summary>

```dart
import 'package:flutter/material.dart';

bool show = true;

void main() => runApp(
      MaterialApp(
        home: Scaffold(
          appBar: AppBar(
            title: const Text('Example'),
          ),
          body: StatefulBuilder(
            builder: (context, setState) {
              return Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                    FilledButton(
                      child: Text('Toggle'),
                      onPressed: () => setState(() => show = !show),
                    ),
                    if (show) DisposeErrorExample(),
                  ],
                ),
              );
            },
          ),
        ),
      ),
    );

class DisposeErrorExample extends StatefulWidget {
  const DisposeErrorExample({super.key});

  @override
  State<DisposeErrorExample> createState() => _DisposeErrorExampleState();
}

class _DisposeErrorExampleState extends State<DisposeErrorExample> {
  @override
  void dispose() async {
    debugPrint('Disposing');
    await Future.delayed(const Duration(milliseconds: 1));
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: 50,
      height: 50,
      child: ColoredBox(
        color: Theme.of(context).primaryColor,
      ),
    );
  }
}
```

</details>

Here is what we get ruining after we press the button:

```
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following assertion was thrown while finalizing the widget tree:
_DisposeErrorExampleState.dispose failed to call super.dispose.
dispose() implementations must always call their superclass dispose() method, to ensure that all the
resources used by the widget are fully released.
```

### Proposal

I propose two changes. 

When running async methods on `setState` there is already a test for asynchronous code, so the same logic could maybe be applied here to output a better error message to the user.

<details>

<summary> Code and output for async code on `setState` </summary>

Output:

```
══╡ EXCEPTION CAUGHT BY GESTURE ╞═══════════════════════════════════════════════════════════════════
The following assertion was thrown while handling a gesture:
setState() callback argument returned a Future.
The setState() method on _StatefulBuilderState#00793 was called with a closure or method that
returned a Future. Maybe it is marked as "async".
Instead of performing asynchronous work inside a call to setState(), first execute the work (without
updating the widget state), and then synchronously update the state inside a call to setState().
```

Code:

```dart
import 'package:flutter/material.dart';

void main() => runApp(
      MaterialApp(
        home: Scaffold(
          appBar: AppBar(
            title: const Text('Example'),
          ),
          body: StatefulBuilder(
            builder: (context, setState) {
              return Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                    FilledButton(
                      child: Text('Toggle'),
                      onPressed: () => setState(
                        () async => await Future.delayed(
                          const Duration(milliseconds: 1),
                        ),
                      ),
                    ),
                  ],
                ),
              );
            },
          ),
        ),
      ),
    );
```

</details>

An easier task I believe would be to add some reference to this on [`State.dispose` docs](https://api.flutter.dev/flutter/widgets/State/dispose.html). Telling what is the preferred way to solve that, if there is one. In the mentioned issue two solutions were suggested, either run all asynchronous code after `super.dispose()` (which IMO is not ideal since that could confuse new users about not always running it in the end) or use `Future.microTask` to run the asynchronous code.