SuperDeclarative
SuperDeclarative

Reputation: 1669

Flutter: How do I change theme brightness at runtime?

I have a MaterialApp with a ThemeData that is initially set to Brightness.light. I'd like to switch the brightness to Brightness.dark at runtime, but when I make that change, only the status bar changes - none of the Flutter widgets actually change their brightness.

How can this behavior be achieved?

To change the ThemeData at runtime, I've created the following StatefulWidget that wraps my MaterialApp and rebuilds it whenever the theme changes:

final ThemeData appTheme = ThemeData(
  brightness: Brightness.light,
);

class ThemeChanger extends StatefulWidget {
  static ThemeChangerState of (BuildContext context) {
    return context.ancestorStateOfType(TypeMatcher<ThemeChangerState>());
  }

  ThemeChanger({
    this.childBuilder,
  });

  final Widget Function(BuildContext, ThemeData) childBuilder;

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

class ThemeChangerState extends State<ThemeChanger> {
  Brightness _brightness = Brightness.light;

  set brightness(Brightness brightness) {
    setState(() {
      _brightness = brightness;
    });
  }

  @override
  Widget build(BuildContext context) {
    return widget.childBuilder(
      context,
      appTheme.copyWith(
        brightness: _brightness
      ),
    );
  }
}

// Then in main.dart
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return DemoTheme(
      childBuilder: (BuildContext context, ThemeData theme) {
        return MaterialApp(
          title: 'Materially Better',
          theme: theme,
          routes: {
            '/': (BuildContext context) {
              return LoginScreen();
            },
            'home': (BuildContext context) {
              return MainScreen();
            }
          },
          debugShowCheckedModeBanner: false,
        );
      },
    );
  }
}

Upvotes: 9

Views: 16467

Answers (6)

flamyoad
flamyoad

Reputation: 565

If you are accessing the newly overridden Theme from context, you will need to wrap the widget inside Builder, so that the children widget is able to get the new Theme.

  Widget build(BuildContext context) {
    return Theme(
      data: widget.isInRoom
          ? Theme.of(context).copyWith(brightness: Brightness.light)
          : Theme.of(context),
      child: Builder(
        builder: (context) {
          return SizedBox();
    );
  }

Upvotes: 0

Luca Conte
Luca Conte

Reputation: 11

Use ThemeMode instead. This solution works fine for me!

class _ChangeBrightnessState extends State<ChangeBrightness> {
  ThemeMode _themeMode = ThemeMode.light;

  _changeThemeMode() {
    if (_themeMode == ThemeMode.light) {
      setState(() {
        _themeMode = ThemeMode.dark;
      });
    } else {
      setState(() {
        _themeMode = ThemeMode.light;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      themeMode: _themeMode,
      theme: ThemeData.light(),
      darkTheme: ThemeData.dark(),
      home: Scaffold(
        appBar: AppBar(
          actions: [
            IconButton(
                onPressed: _changeThemeMode,
                icon: _themeMode == ThemeMode.light
                    ? Icon(Icons.dark_mode)
                    : Icon(Icons.light_mode)),
          ],
        ),
        body: Center(
          child: Text(_themeMode == ThemeMode.light ? "Is light" : "Is Dark"),
        ),
      ),
    );
  }
}

Upvotes: 1

Sen Alexandru
Sen Alexandru

Reputation: 2283

My solution is the following:

  1. Define a variable in main.dart for the brightness and a function that changes the state.

  2. Pass the function to a class and then call it wherever you need.

//main.dart
class _MyAppState extends State<MyApp> {
  Brightness _brightness = Brightness.light;

  void _changeBrightness(Brightness newBrightness) {
    setState(() {
      _brightness = newBrightness;
    });
  }

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      //PASSING THE FUNCTION AS PARAMETER
      Util.changeBrightness = _changeBrightness;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Your app title',
      theme: ThemeData(
        primaryColor: const Color(0xFFb30047),
        brightness: _brightness,
        primarySwatch: Colors.blue,
        accentColor: Colors.white,
        /// ...and so on

In the Util class

class Util {
   static Brightness currentBrightness = Brightness.light;
   static Function changeBrightness;
   // ...further implementations

And then call it when you need. In my case, a switch change in the settings screen:

darkModeChanged(bool newValue) {
   Preferences.setBool('darkMode', newValue);
   setState(() {
     _darkMode = newValue;
     Util.currentBrightness = _darkMode ? Brightness.dark : Brightness.light;        
     Util.changeBrightness(Util.currentBrightness);
   });
}

Upvotes: 3

Asfos22
Asfos22

Reputation: 319

You can change the theme at runtime with ThemeBuilder :

setState(() {
  ThemeBuilder.of(context).changeTheme();
});

Upvotes: -1

Ugurcan Yildirim
Ugurcan Yildirim

Reputation: 6142

You can check out the following library: https://github.com/Norbert515/dynamic_theme

It allows you to change the entire theme data or brightness of your app dynamically at runtime.

Upvotes: 1

SuperDeclarative
SuperDeclarative

Reputation: 1669

The issue is that ThemeData uses its brightness value in the constructor to synthesize a number of other colors, but those colors are not re-calculated when ThemeData is mutated. Therefore, the solution is to instantiate an entirely new ThemeData rather than use appTheme.copyWith(...).

Change this:

appTheme.copyWith(
  brightness: _brightness,
),

To this:

ThemeData(
  brightness: _brightness,
),

Upvotes: 4

Related Questions