Dynamic theme properties in Flutter which can be set in runtime

422

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.

Share:
422
saibot
Author by

saibot

Updated on January 03, 2023

Comments

  • saibot
    saibot over 1 year

    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.

  • saibot
    saibot about 2 years
    That works fine, thanks. The problem is, that I'm using a custom theme schema and it would cause problems if I put that in the "good old" theme from materialapp. How would you use a theme from ThemeSwitcher in exensions? Because I'm not sure how to set values from a externsion dynamically.
  • olexa.le
    olexa.le about 2 years
    ThemeSwitcher lives in parallel with Theme, and they both depend on BuildContext, so to get its value you may create an extension not for TextTheme, but for BuildContext: extension CustomTextTheme on BuildContext { TextStyle header => ThemeSwitcher.of(this).customValue; }
  • saibot
    saibot about 2 years
    Can you please extend the draft code with an example on how to use that? I'm not sure how to define the customValue in the ThemeSwitcher class and also how to use the defined customValue.
  • olexa.le
    olexa.le about 2 years
    Done, I've rewritten the answer to fit more your needs.
  • saibot
    saibot about 2 years
    What do I need to do if I want to add more properties to the CustomThemeData? Because I don't really understand how the @override functions in CustomThemeData work. And if I work with the provider package I can replace the ThemeSwitcherWidget InheritedWidget and ThemeSwitcherWidget with the provider but the CustomThemeData keeps the same, right?
  • olexa.le
    olexa.le about 2 years
    @override functions are required to compare two instances of CustomThemeData, they might generated for you by freezed or provided by equatable packages. Right, you may use provider to provide CustomThemeData without InheritedWidget. I just provided you a solution without 3rd-party dependencies.