I'm new to Flutter and I'm trying to theme my app dynamically so that the user can select his preferred theme either locally or get the system theme.
I managed to get it to work except for when pausing and then resuming to the app after changing the system theme. The theme doesn't update automatically when resuming and I have to run provider.changeToSystemTheme();
by selecting another setting and setting it back for it to update.
I wasn't able to pass final provider = Provider.of<ThemeProvider>(context);
in didChangeAppLifecycleState(AppLifecycleState state)
in order to run provider.changeToSystemTheme();
since it dosen't have a context
to pass into it.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
enum SingingCharacter {light, dark, systemDefault}
var systemBrightness;
void main() => runApp(MaterialAppWithTheme());
class MaterialAppWithTheme extends StatefulWidget {
const MaterialAppWithTheme({Key? key}) : super(key: key);
@override
State<MaterialAppWithTheme> createState() => _MaterialAppWithThemeState();
}
class _MaterialAppWithThemeState extends State<MaterialAppWithTheme> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
switch (state) {
case AppLifecycleState.resumed:
break;
case AppLifecycleState.inactive:
break;
case AppLifecycleState.paused:
break;
case AppLifecycleState.detached:
break;
}
}
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => ThemeProvider(),
builder: (context, _) {
final themeProvider = Provider.of<ThemeProvider>(context);
return MaterialApp(
home: NinjaCard2(),
themeMode: themeProvider.themeMode,
theme: MyThemes.lightTheme,
darkTheme: MyThemes.darkTheme,
);
}
);
}
}
class ThemeProvider extends ChangeNotifier {
ThemeMode themeMode = ThemeMode.system;
void changeToLightTheme() {
themeMode = ThemeMode.light;
notifyListeners();
}
void changeToDarkTheme() {
themeMode = ThemeMode.dark;
notifyListeners();
}
void changeToSystemTheme() {
themeMode = ThemeMode.system;
notifyListeners();
}
}
class MyThemes{
static final darkTheme = ThemeData(
scaffoldBackgroundColor: Colors.black,
drawerTheme: DrawerThemeData(backgroundColor: MaterialStateColor.resolveWith((states) => Colors.black)),
radioTheme: RadioThemeData(fillColor: MaterialStateColor.resolveWith((states) => Colors.blue)),
dividerColor: Colors.white,
colorScheme: ColorScheme.dark().copyWith(
primary: Colors.blue,
onPrimary: Colors.white,
secondary: Colors.blue,
onSecondary: Colors.white,
surface: Color(0xff303031),
)
);
static final lightTheme = ThemeData(
colorScheme: ColorScheme.light().copyWith(
primary: Colors.blue,
onPrimary: Colors.white,
secondary: Colors.blue,
onSecondary: Colors.white,
)
);
}
class NinjaCard2 extends StatefulWidget {
const NinjaCard2({Key? key}) : super(key: key);
@override
State<NinjaCard2> createState() => _NinjaCard2State();
}
class _NinjaCard2State extends State<NinjaCard2> {
SingingCharacter? _character = SingingCharacter.systemDefault;
@override
Widget build(BuildContext context) {
final provider = Provider.of<ThemeProvider>(context);
return Scaffold(
appBar: AppBar(
title: const Text('Settings'),
elevation: 1.5,
centerTitle: true,
),
body: Column(
children: <Widget>[
ListTile(
title: const Text('Light'),
leading: Radio<SingingCharacter>(
value: SingingCharacter.light,
groupValue: _character,
onChanged: (SingingCharacter? value) {
setState(() {
_character = value;
provider.changeToLightTheme();
});
},
),
),
ListTile(
title: const Text('Dark'),
leading: Radio<SingingCharacter>(
value: SingingCharacter.dark,
groupValue: _character,
onChanged: (SingingCharacter? value) {
provider.changeToDarkTheme();
setState(() {
_character = value;
});
},
),
),
ListTile(
title: const Text('System Default'),
leading: Radio<SingingCharacter>(
value: SingingCharacter.systemDefault,
groupValue: _character,
onChanged: (SingingCharacter? value) {
provider.changeToSystemTheme();
setState(() {
_character = value;
});
},
),
),
],
),
);
}
}
Is there any other way to achieve what looking for?
I would also appreciate links to study materials in the answers.
CodePudding user response:
Inside a State
class, you almost always have context
available. Besides initState
, all the other methods can call it (without any downsides, I mean. Inside initState
, you can use context
as well, but there are some counter-indications for doing that in some cases).
A full explanation of State: https://api.flutter.dev/flutter/widgets/State-class.html
When in doubt, always look for: https://api.flutter.dev and https://api.dart.dev/
Also, a great way of learning new stuff is over Decoding Flutter playlist: https://youtube.com/playlist?list=PLjxrf2q8roU1fRV40Ec8200rX6OuQkmnl
CodePudding user response:
After checking the State
documentation, I was able to find the solution.
All I had to do is add:
@override
void didChangeDependencies() {
super.didChangeDependencies();
}
after:
@override
void initState() {
super.initState();
}
Which gives us:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
enum SingingCharacter {light, dark, systemDefault}
void main() => runApp(MaterialAppWithTheme());
class MaterialAppWithTheme extends StatefulWidget {
const MaterialAppWithTheme({Key? key}) : super(key: key);
@override
State<MaterialAppWithTheme> createState() => _MaterialAppWithThemeState();
}
class _MaterialAppWithThemeState extends State<MaterialAppWithTheme> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
}
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => ThemeProvider(),
builder: (context, _) {
final themeProvider = Provider.of<ThemeProvider>(context);
return MaterialApp(
home: NinjaCard2(),
themeMode: themeProvider.themeMode,
theme: MyThemes.lightTheme,
darkTheme: MyThemes.darkTheme,
);
}
);
}
}
class ThemeProvider extends ChangeNotifier {
ThemeMode themeMode = ThemeMode.system;
void changeToLightTheme() {
themeMode = ThemeMode.light;
notifyListeners();
}
void changeToDarkTheme() {
themeMode = ThemeMode.dark;
notifyListeners();
}
void changeToSystemTheme() {
themeMode = ThemeMode.system;
notifyListeners();
}
}
class MyThemes{
static final darkTheme = ThemeData(
scaffoldBackgroundColor: Colors.black,
drawerTheme: DrawerThemeData(backgroundColor: MaterialStateColor.resolveWith((states) => Colors.black)),
radioTheme: RadioThemeData(fillColor: MaterialStateColor.resolveWith((states) => Colors.blue)),
dividerColor: Colors.white,
colorScheme: ColorScheme.dark().copyWith(
primary: Colors.blue,
onPrimary: Colors.white,
secondary: Colors.blue,
onSecondary: Colors.white,
surface: Color(0xff303031),
)
);
static final lightTheme = ThemeData(
colorScheme: ColorScheme.light().copyWith(
primary: Colors.blue,
onPrimary: Colors.white,
secondary: Colors.blue,
onSecondary: Colors.white,
)
);
}
class NinjaCard2 extends StatefulWidget {
const NinjaCard2({Key? key}) : super(key: key);
@override
State<NinjaCard2> createState() => _NinjaCard2State();
}
class _NinjaCard2State extends State<NinjaCard2> {
SingingCharacter? _character = SingingCharacter.systemDefault;
@override
Widget build(BuildContext context) {
final provider = Provider.of<ThemeProvider>(context);
return Scaffold(
appBar: AppBar(
title: const Text('Settings'),
elevation: 1.5,
centerTitle: true,
),
body: Column(
children: <Widget>[
ListTile(
title: const Text('Light'),
leading: Radio<SingingCharacter>(
value: SingingCharacter.light,
groupValue: _character,
onChanged: (SingingCharacter? value) {
setState(() {
_character = value;
provider.changeToLightTheme();
});
},
),
),
ListTile(
title: const Text('Dark'),
leading: Radio<SingingCharacter>(
value: SingingCharacter.dark,
groupValue: _character,
onChanged: (SingingCharacter? value) {
provider.changeToDarkTheme();
setState(() {
_character = value;
});
},
),
),
ListTile(
title: const Text('System Default'),
leading: Radio<SingingCharacter>(
value: SingingCharacter.systemDefault,
groupValue: _character,
onChanged: (SingingCharacter? value) {
provider.changeToSystemTheme();
setState(() {
_character = value;
});
},
),
),
],
),
);