Repository: flutter
Issue: Reusing state logic is either too verbose or too difficult
Labels: c: new feature,framework,dependency: dart,customer: crowd,c: proposal,P3,team-framework,triaged-framework

[.](https://github.com/flutter/flutter/issues/51752#issuecomment-675844037)

Related to the discussion around hooks #25280 

TL;DR: It is difficult to reuse `State` logic. We either end up with a complex and deeply nested `build` method or have to copy-paste the logic across multiple widgets.

It is neither possible to reuse such logic through mixins nor functions.

# Problem

Reusing a `State` logic across multiple `StatefulWidget` is very difficult, as soon as that logic relies on multiple life-cycles.

A typical example would be the logic of creating a `TextEditingController` (but also `AnimationController`, implicit animations, and many more). That logic consists of multiple steps:
- defining a variable on `State`.
  ```dart
  TextEditingController controller;
  ```
- creating the controller (usually inside initState), with potentially a default value:
   ```dart
  @override
  void initState() {
    super.initState();
    controller = TextEditingController(text: 'Hello world');
  }
  ```
- disposed the controller when the `State` is disposed:
  ```dart
  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
  ```

- doing whatever we want with that variable inside `build`.
- (optional) expose that property on `debugFillProperties`:
   ```dart
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DiagnosticsProperty('controller', controller));
  }
  ```


This, in itself, is not complex. The problem starts when we want to scale that approach.
A typical Flutter app may have dozens of text-fields, which means this logic is duplicated multiple times.


Copy-pasting this logic everywhere "works", but creates a weakness in our code:
- it can be easy to forget to rewrite one of the steps (like forgetting to call `dispose`)
- it adds a lot of noise in the code



## The Mixin issue

The first attempt at factorizing this logic would be to use a mixin:

```dart
mixin TextEditingControllerMixin<T extends StatefulWidget> on State<T> {
  TextEditingController get textEditingController => _textEditingController;
  TextEditingController _textEditingController;

  @override
  void initState() {
    super.initState();
    _textEditingController = TextEditingController();
  }

  @override
  void dispose() {
    _textEditingController.dispose();
    super.dispose();
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DiagnosticsProperty('textEditingController', textEditingController));
  }
}
```

Then used this way:

```dart
class Example extends StatefulWidget {
  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example>
    with TextEditingControllerMixin<Example> {
  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: textEditingController,
    );
  }
}
```

But this has different flaws:

- A mixin can be used only once per class. If our `StatefulWidget` needs multiple `TextEditingController`, then we cannot use the mixin approach anymore. 

- The "state" declared by the mixin may conflict with another mixin or the `State` itself.
  More specifically, if two mixins declare a member using the same name, there will be a conflict.
  Worst-case scenario, if the conflicting members have the same type, this will silently fail.

This makes mixins both un-ideal and too dangerous to be a true solution.

## Using the "builder" pattern

Another solution may be to use the same pattern as `StreamBuilder` & co.

We can make a `TextEditingControllerBuilder` widget, which manages that controller. Then our `build` method can use it freely.

Such a widget would be usually implemented this way:

```dart
class TextEditingControllerBuilder extends StatefulWidget {
  const TextEditingControllerBuilder({Key key, this.builder}) : super(key: key);

  final Widget Function(BuildContext, TextEditingController) builder;

  @override
  _TextEditingControllerBuilderState createState() =>
      _TextEditingControllerBuilderState();
}

class _TextEditingControllerBuilderState
    extends State<TextEditingControllerBuilder> {
  TextEditingController textEditingController;

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(
        DiagnosticsProperty('textEditingController', textEditingController));
  }

  @override
  void dispose() {
    textEditingController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return widget.builder(context, textEditingController);
  }
}
```


Then used as such:


```dart
class Example extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return TextEditingControllerBuilder(
      builder: (context, controller) {
        return TextField(
          controller: controller,
        );
      },
    );
  }
}
```


This solves the issues encountered with mixins. But it creates other issues.

- The usage is very verbose. That's effectively 4 lines of code + two levels of indentation for a single variable declaration.
  This is even worse if we want to use it multiple times. While we can create a `TextEditingControllerBuilder` inside another once, this drastically decrease the code readability:

  ```dart
  @override
  Widget build(BuildContext context) {
    return TextEditingControllerBuilder(
      builder: (context, controller1) {
        return TextEditingControllerBuilder(
          builder: (context, controller2) {
            return Column(
              children: <Widget>[
                TextField(controller: controller1),
                TextField(controller: controller2),
              ],
            );
          },
        );
      },
    );
  }
  ```

  That's a very indented code just to declare two variables.
- This adds some overhead as we have an extra `State` and `Element` instance.

- It is difficult to use the `TextEditingController` outside of `build`.
  If we want a `State` life-cycles to perform some operation on those controllers, then we will need a `GlobalKey` to access them. For example:

  ```dart
  class Example extends StatefulWidget {
    @override
    _ExampleState createState() => _ExampleState();
  }

  class _ExampleState extends State<Example> {
    final textEditingControllerKey =
        GlobalKey<_TextEditingControllerBuilderState>();

    @override
    void didUpdateWidget(Example oldWidget) {
      super.didUpdateWidget(oldWidget);

      if (something) {
        textEditingControllerKey.currentState.textEditingController.clear();
      }
    }

    @override
    Widget build(BuildContext context) {
      return TextEditingControllerBuilder(
        key: textEditingControllerKey,
        builder: (context, controller) {
          return TextField(controller: controller);
        },
      );
    }
  }
  ```