Dynamic theme properties in Flutter which can be set in runtime

Issue

I would like to create my own theme properties which can be set in runtime dynamically.
I tried to create an extension for the TextTheme which looks like that:


extension CustomTextTheme on TextTheme {
  TextStyle get heading => themeMode == ThemeMode.light
      ? TextStyle(
          color: GlobalTheme.defaultLightTheme.textTheme.headline.color,
          fontSize: GlobalTheme.defaultLightTheme.textTheme.headline.fontSize,
        )
      : TextStyle(
          color: GlobalTheme.defaultDarkTheme.textTheme.headline.color,
          fontSize: GlobalTheme.defaultLightTheme.textTheme.headline.fontSize,
        );
}

The question is how I can change the extension properties on runtime dynamically.
What I want to archive with this is, that I can load a "theme config" from the server and set that theme on each device dynamically.

Solution

To pass and get values inside the widget tree you need InheritedWidget.

This is a special type of Widgets that just transfer the information between widgets (like Theme delivers ThemeData).
You can not extend ThemeData with new fields as extensions will not trigger updates on Theme.
But you can create your own CustomTheme which will coexist with the original.

class CustomThemeData {
  const CustomThemeData(this.heading);

  final TextStyle heading;

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is CustomThemeData &&
          runtimeType == other.runtimeType &&
          heading == other.heading;

  @override
  int get hashCode => heading.hashCode;
}

Now create an InheritedWidget that wll provide the custom theme data value (via of) and will add a possibility to update the data (via update)

class CustomTheme extends InheritedWidget {
  const CustomTheme({
    Key? key,
    required this.data,
    required this.onUpdate,
    required Widget child,
  }) : super(key: key, child: child);

  final CustomThemeData data;
  final void Function(CustomThemeData) onUpdate;

  static CustomThemeData of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<CustomTheme>()!.data;
  }

  static void update(BuildContext context, CustomThemeData data) {
    context.dependOnInheritedWidgetOfExactType<CustomTheme>()!.onUpdate(data);
  }

  @override
  bool updateShouldNotify(covariant CustomTheme oldWidget) {
    return data != oldWidget.data;
  }
}

Here is a holder for custom theme

class ThemeSwitcherWidget extends StatefulWidget {
  final CustomThemeData initialTheme;
  final Widget child;

  const ThemeSwitcherWidget({
    Key? key,
    required this.initialTheme,
    required this.child,
  }) : super(key: key);

  @override
  _ThemeSwitcherWidgetState createState() => _ThemeSwitcherWidgetState();
}

class _ThemeSwitcherWidgetState extends State<ThemeSwitcherWidget> {
  CustomThemeData? _updatedTheme;

  @override
  Widget build(BuildContext context) {
    return CustomTheme(
      data: _updatedTheme ?? widget.initialTheme,
      onUpdate: (newData) => setState(() {
        _updatedTheme = newData;
      }),
      child: widget.child,
    );
  }
}

And here is an example on how to use all this beauty:

void main() {
  runApp(
    const ThemeSwitcherWidget(
      initialTheme: CustomThemeData(TextStyle()),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(
                'text',
                style: CustomTheme.of(context).heading,
              ),
              ElevatedButton(
                  onPressed: () {
                    CustomTheme.update(
                        context,
                        const CustomThemeData(TextStyle(
                          color: Colors.red,
                          fontSize: 44,
                        )));
                  },
                  child: const Text('Change theme')),
            ],
          ),
        ),
      ),
    );
  }
}

To make the code less verbose you may use provider which will do all that magic with updates for you.

Answered By – olexa.le

Answer Checked By – Dawn Plyler (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *