My goal is to create an app where the user can choose his preferred theme. I'm saving the user's choice with shared preferences so I can load it the next app start. The user can either select: - Dark Mode (Independent from the OS Settings) - Light Mode (Independent from the OS Settings) - System (Changes between Dark Mode and Light mode depending on the OS settings) With the help of BLoC, I almost achieved what I want. But the problem is that I need to pass the brightness inside my Bloc event. And to get the system (OS) brightness I need to make use of
MediaQuery.of(context).platformBrightness
But the Bloc gets initiated before MaterialApp so that MediaQuery is unavailable. Sure I can pass the brightness later(from a child widget of MaterialApp) but then (for example, if the user has dark mode activated) it goes from light to dark but visible for a really short time for the user(Because inside the InitialState I passed in light mode).
class MyApp extends StatelessWidget {
final RecipeRepository recipeRepository;
MyApp({Key key, @required this.recipeRepository})
: assert(recipeRepository != null),
super(key: key);
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<ThemeBloc>(create: (context) =>
ThemeBloc(),),
],
child: BlocBuilder<ThemeBloc, ThemeState>(
builder: (context, state){
return MaterialApp(
theme: state.themeData,
title: 'Flutter Weather',
localizationsDelegates: [
FlutterI18nDelegate(fallbackFile: 'en',),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate
],
supportedLocales: [
const Locale("en"),
const Locale("de"),
],
home: Home(recipeRepository: recipeRepository),
);
},
),
);
}
}
ThemeBloc:
class ThemeBloc extends Bloc<ThemeEvent, ThemeState> {
@override
ThemeState get initialState =>
ThemeState(themeData: appThemeData[AppTheme.Bright]);
@override
Stream<ThemeState> mapEventToState(
ThemeEvent event,
) async* {
if (event is LoadLastTheme) {
ThemeData themeData = await _loadLastTheme(event.brightness);
yield ThemeState(themeData: themeData);
}
if (event is ThemeChanged) {
await _saveAppTheme(event.theme);
yield ThemeState(themeData: appThemeData[event.theme]);
}
}
Future<ThemeData> _loadLastTheme(Brightness brightness) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
String themeString = prefs.getString(SharedPrefKeys.appThemeKey);
print("saved theme: $themeString");
if ((prefs.getString(SharedPrefKeys.appThemeKey) != null) &&
themeString != "AppTheme.System") {
switch (themeString) {
case "AppTheme.Bright":
{
return appThemeData[AppTheme.Bright];
}
break;
///Selected dark mode
case "AppTheme.Dark":
{
return appThemeData[AppTheme.Dark];
}
break;
}
}
print("brightness: $brightness");
if (brightness == Brightness.dark) {
return appThemeData[AppTheme.Dark];
} else {
return appThemeData[AppTheme.Bright];
}
}
Future<void> _saveAppTheme(AppTheme appTheme) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString(SharedPrefKeys.appThemeKey, appTheme.toString());
}
}
If you absolutely must do it like this, you can get MediaQuery
data directly from the low-level platform object like this:
Before Flutter 3.10
final brightness = MediaQueryData.fromWindow(WidgetsBinding.instance.window).platformBrightness;
Flutter 3.10+:
final brightness = PlatformDispatcher.instance.platformBrightness;
However, I would strongly recommend you consider that if you need access to MediaQuery
from within your bloc, you should instead move your BlocProvider
to get instantiated after your MaterialApp
so you can access MediaQuery
normally.
After Flutter 3.10, if for whatever reason you really want to get this value manually and react to system changes, you can listen to the onPlatformBrightnessChanged
event:
PlatformDispatcher.instance.onPlatformBrightnessChanged(() => {
final brightness = PlatformDispatcher.instance.platformBrightness;
// ... Do something with `brightness`
});
Though at this point, you really are just reinventing the wheel. The option is there for people who need it, but you shouldn't use it unless you are absolutely sure you can't use MediaQuery
.