Reputation: 1669
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
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
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
Reputation: 2283
My solution is the following:
Define a variable in main.dart
for the brightness and a function that changes the state.
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
Reputation: 319
You can change the theme at runtime with ThemeBuilder :
setState(() {
ThemeBuilder.of(context).changeTheme();
});
Upvotes: -1
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
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