Чисто переопределяющие части темы локально в Flutter

У меня есть виджет, у которого есть два TextField как потомки. Я хотел бы применить тот же стиль к этим TextField. Я понимаю, что правильный способ сделать это - применить локализованную тему к моему дереву виджета. Следующей является моя попытка. Это фрагмент кода из моей функции build корневого виджета. Разве нет более чистого способа сделать это?

final ThemeData _themeData = Theme.of(context);
return Theme( // HACK
  data: _themeData.copyWith(
    inputDecorationTheme: InputDecorationTheme(
      border: OutlineInputBorder(),
    ),
    textTheme: _themeData.textTheme.copyWith(
      subhead: _themeData.textTheme.subhead.copyWith(
        fontSize: 30.0,
      ),
    ),
  ),
  child: _buildTheRestOfMyWidgetTree(context),
);

То, что меня раздражает, заключается в том, что для переопределения одного свойства (_themeData.textTheme.subhead.fontSize) я должен явно и вручную создавать копии трех промежуточных структур данных (_themeData, _themeData.textTheme, затем _themeData.textTheme.subhead).

Ответ 1

Хотя я могу понять разочарование в необходимости "скопировать" все, так вы должны это сделать.

Данные неизменяемы в Flutter. Вы не можете их мутировать, вы вынуждены клонировать их с разными свойствами.

Поэтому ваше предположение верно: если вы хотите изменить вложенное свойство, вам также нужно клонировать всех его родителей. Что приводит к:

final ThemeData theme = Theme.of(context);
theme.copyWith(
  textTheme: theme.textTheme.copyWith(
    subhead: theme.textTheme.subhead.copyWith(
      fontSize: 30.0,
    ),
  ),
);

Опять же: вы не можете этого избежать.

Ответ 2

Это поможет, если вы упакуете эту часть кода и сделаете его виджетом, чтобы ваше дерево было чище. Это как это сделано в этом примере.

class TextFieldOverride extends StatelessWidget {
  const TextFieldOverride({this.child});
  final Widget child;
  @override
  Widget build(BuildContext context) {
    final themeData = Theme.of(context);
    return Theme(
      child: child,
      data: themeData.copyWith(
        inputDecorationTheme: InputDecorationTheme(
          border: OutlineInputBorder()),
      textTheme: themeData.textTheme.copyWith(
        subhead: themeData.textTheme.subhead.copyWith(
          fontSize: 30.0))));
  }
}

...

TextFieldOverride(
  child: TextField(...)
)

Или, если есть несколько мест, код будет дублироваться, вы можете просто внести изменения напрямую:

...
child: TextField(
  style: Theme.of(context).textTheme.subhead.copyWith(fontSize: 30.0),
  decoration: InputDecoration(border: OutlineInputBorder(),
    ...
  )
)

Или, возможно, лучший выбор - создать функцию, которая делает выше для вас.

TextField buildTextField(BuildContext context) => TextField(
  style: Theme.of(context).textTheme.subhead.copyWith(fontSize: 30.0),
  decoration: InputDecoration(border: OutlineInputBorder(),
    ...
  )
)