skip to Main Content

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.

Dark Mode:
enter image description here

Light Mode:
enter image description here

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


  1. 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/).

    ThemeData.dark().copyWith(
          appBarTheme: AppBarTheme(
            color: APPColors.Main.black,
            elevation: 0,
            systemOverlayStyle: SystemUiOverlayStyle.light, // modified system overlay style for dark theme
            iconTheme: IconThemeData(color: APPColors.Main.white),
          ),
          navigationBarTheme: NavigationBarThemeData(
            backgroundColor: APPColors.Main.black, // modified navigation bar's background color
            indicatorColor: APPColors.Main.white,
          ),
    )
    

    You can give specific status appbar color and navigation bar color with using this code. This code for dark theme.

    Login or Signup to reply.
  2. 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:

    • Wrap the content of your NotificationsScreen with a Builder widget to
      obtain a new BuildContext.
    • Use the obtained BuildContext to update the system overlays in the
      setThemeMode method.

    Here’s the modified code:

    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: Builder(
            builder: (BuildContext context) {
              return const Center(
                child: Text('Notifications feature is coming soon!'),
              );
            },
          ),
        );
      }
    }
    
    class SelectThemeProvider with ChangeNotifier {
      int _selectedTheme;
    
      SelectThemeProvider(this._selectedTheme);
    
      int get selectedTheme => _selectedTheme;
    
      Future<void> setThemeMode(int number) async {
        _selectedTheme = number;
        notifyListeners();
    
        // Use a Builder to get a new BuildContext within the notifications screen
        Builder(builder: (BuildContext context) {
          bool isDarkMode = _selectedTheme == 2;
          SystemChrome.setSystemUIOverlayStyle(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,
          ));
        });
      }
    }
    

    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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search