I am building a flutter mobile app and I would like the status bar and navigation bar themes to match the current theme of the app. The app theme can be changed by the user. Examples in the screenshots attached.
I used AnnotatedRegion to change the status and navigation bar themes in the MaterialApp. If I am on the home page and change the theme via the drawer, it works as expected, the status bar and the navigation bar changes. However, if I navigate out of the home page by clicking the floating action button, and then change the theme there via the drawer, the status bar and navigation bars themes do not change to match the theme. Note: I used Navigator.of(context) .pushReplacement(MaterialPageRoute(builder: (context) { return const NotificationsScreen();
to move to the Notifications screen.
Here is the code:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => SelectThemeProvider(2),
child:
Consumer<SelectThemeProvider>(builder: (context, themeProvider, _) {
bool isDarkMode = themeProvider.selectedTheme == 2;
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
darkTheme: ThemeData.dark(),
themeMode: currentTheme(themeProvider.selectedTheme),
theme: ThemeData.light().copyWith(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarColor:
isDarkMode ? const Color(0xff121212) : Colors.white,
statusBarIconBrightness:
isDarkMode ? Brightness.light : Brightness.dark,
statusBarBrightness:
isDarkMode ? Brightness.dark : Brightness.light,
systemNavigationBarColor:
isDarkMode ? const Color(0xff121212) : Colors.white,
systemNavigationBarIconBrightness:
isDarkMode ? Brightness.light : Brightness.dark,
),
child: const MyHomePage(title: 'Flutter Demo Home Page'),
),
);
}),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
drawer: const MyDrawer(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.of(context)
.pushReplacement(MaterialPageRoute(builder: (context) {
return const NotificationsScreen();
}));
},
// _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
ThemeMode currentTheme(int choice) {
switch (choice) {
case 1:
return ThemeMode.light;
case 2:
return ThemeMode.dark;
default:
return ThemeMode.system;
}
}
//The drawer widget
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:test_app/select_theme_provider.dart';
class MyDrawer extends StatefulWidget {
const MyDrawer({super.key});
@override
State<MyDrawer> createState() => _MyDrawerState();
}
class _MyDrawerState extends State<MyDrawer> {
@override
Widget build(BuildContext context) {
SelectThemeProvider themeProvider = Provider.of<SelectThemeProvider>(
context,
listen: false,
);
return Drawer(
elevation: 3.0,
width: MediaQuery.of(context).size.width * 0.6,
child: ListView(
padding: EdgeInsets.zero,
children: [
ListTile(
leading: const Icon(Icons.home),
title: const Text('Home'),
onTap: () {},
),
ListTile(
leading: const Icon(Icons.settings),
title: const Text('Settings'),
onTap: () {
Navigator.of(context).pop();
},
),
ListTile(
leading: const Icon(Icons.person_outline),
title: const Text('Profile'),
onTap: () {},
),
Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
' Theme',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontWeight: FontWeight.bold,
),
),
RadioListTile(
title: const Text('Light'),
value: 1,
groupValue: themeProvider.selectedTheme,
activeColor: Colors.purple,
onChanged: (value) async {
await themeProvider.setThemeMode(value!);
},
),
RadioListTile(
title: const Text('Dark'),
value: 2,
groupValue: themeProvider.selectedTheme,
activeColor: Colors.purple,
onChanged: (value) async {
await themeProvider.setThemeMode(value!);
},
),
RadioListTile(
title: const Text('System'),
value: 3,
groupValue: themeProvider.selectedTheme,
activeColor: Colors.purple,
onChanged: (value) async {
await themeProvider.setThemeMode(value!);
},
),
],
),
ListTile(
leading: const Icon(Icons.logout),
title: const Text('Log Out'),
onTap: () {},
),
],
),
);
}
}
//Here is the SelectedThemeProvider
import 'package:flutter/cupertino.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SelectThemeProvider with ChangeNotifier {
int _selectedTheme;
// Constructor for the SelectThemeProvider
SelectThemeProvider(this._selectedTheme);
int get selectedTheme => _selectedTheme;
Future<void> setThemeMode(int number) async {
_selectedTheme = number;
// SharedPreferences prefs = await SharedPreferences.getInstance();
// await prefs.setInt('storedTheme', number);
notifyListeners();
}
}
// Here is the Notifications Screen
import 'package:flutter/material.dart';
import 'package:test_app/my_drawer.dart';
class NotificationsScreen extends StatefulWidget {
const NotificationsScreen({super.key});
@override
State<NotificationsScreen> createState() => _NotificationsScreenState();
}
class _NotificationsScreenState extends State<NotificationsScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
drawer: const MyDrawer(),
body: const Center(
child: Text('Notifications feature is coming soon!'),
),
);
}
}
What is the best way to implement this? I believe that there is an elegant solution someone has that I am missing.
2
Answers
First of all you can give custom themes to your application. For this you can check this article (https://mobikul.com/custom-theme-in-flutter/).
You can give specific status appbar color and navigation bar color with using this code. This code for dark theme.
To ensure that the status bar and navigation bar themes are updated when changing the theme from a different screen, you can use the Builder widget to get a new BuildContext that has access to the nearest Scaffold ancestor. Then, you can use this BuildContext to update the system overlays. Here’s how you can modify your code:
obtain a new BuildContext.
setThemeMode method.
Here’s the modified code:
With this modification, the Builder widget in the NotificationsScreen will provide a new BuildContext that has access to the nearest Scaffold ancestor, and you can use this context to update the system overlays when changing the theme.