How to add a switch to change Flutter App theme?

2.7k Views Asked by At

I am new to flutter and facing issues while trying to incorporate a button on the Appbar that changes the theme of the entire app.

This is the main.dart file code.

import 'package:flutter/material.dart';
import 'package:qrpay_app/routes/Routes.dart';
import 'module/splash/splash_view.dart';
import 'module/buy/buy_list_view.dart';
import 'module/sell/sell_list_view.dart';
import 'module/shop/shop_list_view.dart';
import 'module/account/account_list_view.dart';
import 'module/contact/contact_list_view.dart';
import 'module/help/help_list_view.dart';
import 'module/receipts/receipts_list_view.dart';
import 'module/about us/about_us_view.dart';
import 'package:qrpay_app/Themes/appThemes.dart';

import 'module/qr/qr_view.dart';

void main() {
  runApp(new MaterialApp(
    title: 'QRPAY Touch-Free',
    theme: new ThemeData(primarySwatch: Colors.red),
    home: SplashPage(),
    routes: {
      Routes.splash: (context) => SplashPage(),
      Routes.buy: (context) => BuyPage(),
      Routes.sell: (context) => SellPage(),
      Routes.shop: (context) => ShopPage(),
      Routes.qr: (context) => QRPage(),
      Routes.account: (context) => AccountPage(),
      Routes.contact: (context) => ContactPage(),
      Routes.help: (context) => HelpPage(),
      Routes.receipts: (context) => ReceiptsPage(),
      Routes.about_us: (context) => AboutUsPage(),
    },
  ));
}

This is the appThemes.dart code:

import 'package:flutter/material.dart';

class AppThemes {
  // Simple constructor
  AppThemes._();

  static final ThemeData highContrast = ThemeData(
    primarySwatch: Colors.black,
    primaryTextTheme: TextTheme(
      headline6: TextStyle(color: Colors.white),
    ),
    scaffoldBackgroundColor: Colors.white,
    appBarTheme: AppBarTheme(
      color: Colors.black,
      iconTheme: IconThemeData(
        color: Colors.white,
      ),
      elevation: 30.0,
    ),
  );

  static final ThemeData normalContrast = ThemeData(
    scaffoldBackgroundColor: Colors.white,
    appBarTheme: AppBarTheme(
      color: Colors.red,
      iconTheme: IconThemeData(
        color: Colors.white,
      ),
    ),
  );
}

I have routed the home to splash_view.dart. This is the appThemes.dart code:

import 'package:flutter/material.dart';
import 'package:qrpay_app/widget/drawer.dart';
import 'package:qrpay_app/Themes/appThemes.dart';

class SplashPage extends StatefulWidget {
  static const String routeName = '/splash';
  @override
  _SplashPageState createState() => _SplashPageState();
}

class _SplashPageState extends State<SplashPage> {
  void changeContrast() {
    print("High contrast button clicked\nChanging app contrast\n");
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("QRPAY Touch-Free"),
        elevation: 30.0,
        actions: <Widget>[
          ElevatedButton(
              onPressed: () {
                changeContrast();
              },
              child: Text('High Contrast')),
        ],
      ),
      drawer: AppDrawer(),
      body: new Center(
        child: new Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Container(
              child: new Image.asset(
                'res/images/qplogo.png',
                width: MediaQuery.of(context).size.width * .9,
                fit: BoxFit.cover,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

I have added an elevated button that needs to change the theme of the entire app. Please help with mapping the functionality based on my dart files.

Also, I am confused about how to add the change in state

I tried working out with 'provider' but I am getting confused.

Please someone help me out. Also, please let me know if you require anything else to better understand my problem.

I appreciate you all taking out the time and helping me out.

Thank You.

3

There are 3 best solutions below

3
On

You can read an article about Flutter Stream here: https://medium.com/@ayushpguptaapg/using-streams-in-flutter-62fed41662e4

Add a boolean value to track current theme

bool isHighContrast = false;

Then when your switch is on, just simply call:

// switch is on, toggle isHighContrast. isHighContrast now equals to true
controller.add(isHighContrast);

And then listen for changes to update entire UI:

controller.stream.listen({
  setState({
    theme = AppTheme.highContrast;
  });
})
1
On

The simplest approach is to use Provider.

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(
    MultiProvider(
      providers: [
      // Used MultiProvider incase you have other providers
        ChangeNotifierProvider<ThemeDataProvider>(
          create: (_) => ThemeDataProvider(),
        ),
      ],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    ThemeDataProvider themeDataProvider = Provider.of(context);

    // Pull the theme data from the provider and make a few modification
    // The modifications are for illustration only.  Not required.
    final ThemeData currentTheme = themeDataProvider.themeData.copyWith(
      scaffoldBackgroundColor: themeDataProvider.isDarkTheme ? Colors.yellow[700] : Colors.yellow[300],
      appBarTheme: themeDataProvider.themeData.appBarTheme,
      cardTheme: themeDataProvider.themeData.cardTheme,
    );
    return MaterialApp(
      color: Colors.yellow[100],
      title: 'MyApp',
      theme: currentTheme, //set your theme
      initialRoute: setupRoute,
      onGenerateRoute: Router.generateRoute,
    );
  }
}


class ThemeDataProvider with ChangeNotifier {
  Future<SharedPreferences> _prefs = SharedPreferences.getInstance();

  SharedPreferences prefs;
  bool isInitialized;
  bool _useDarkTheme;
  double _appMargin;
  int _animationDuration = 200;
  ThemeData _themeData;

  ThemeDataProvider() {
    isInitialized = false;
    _initialize();
  }

  void _initialize() async {
    prefs = await _prefs;
    await _loadPrefs();
    _themeData = _buildThemeData();
    _appMargin = 0.0;
    isInitialized = true;
  }

  ThemeData get themeData => _themeData;

  bool get isDarkTheme => _useDarkTheme ?? true;
  double get appMargin => _appMargin;
  int get animDuration => _animationDuration;

  ///
  /// Set the application working margin.
  ///
  void setAppMargin(double appMargin) {
    _appMargin = appMargin;
  }

  void toggleTheme() {
    _useDarkTheme = !_useDarkTheme;
    _savePrefs();
    _themeData = _buildThemeData();
    notifyListeners();
  }

  Future _loadPrefs() async {
    prefs = await _prefs;
    _useDarkTheme = prefs.getBool("useDarkMode") ?? true;
    _themeData = _buildThemeData();
    notifyListeners();
  }

  void _savePrefs() async {
    prefs = await _prefs;
    prefs.setBool("useDarkMode", _useDarkTheme);
  }

// Build your theme data here
      ThemeData _buildThemeData() {
        return ThemeData(
          primarySwatch: isDarkTheme
              ? MaterialColor(4280361249, {
                  50: Color(0xfff2f2f2),
    ...

Add the switch whereever you like and on click call the toggleTheme method.

This is not functionng code. It is an edited version of my production code.

0
On

I use StateProvider in flutter_riverpod package to achieve this.

// Add the global provider
final appThemeProvider = StateProvider<ThemeData>((ref) {
  return App.getDefaultThemeData();
});

class App extends ConsumerWidget {
//                ^^^^^^^^^^^^^^
  @override
  Widget build(BuildContext context, WidgetRef ref) {
//                                   ^^^^^^^^^^^^^
    final currentTheme = ref.watch(appThemeProvider).state;

    return MaterialApp(
      color: Colors.yellow[100],
      title: 'MyApp',
      theme: currentTheme, //set your theme
      initialRoute: setupRoute,
      onGenerateRoute: Router.generateRoute,
    );

    // this is default theme
    static ThemeData getDefaultThemeData() {
      return ThemeData(
        primarySwatch: MaterialColor(0xFF00FF00, mappingColor),
        backgroundColor: MaterialColor(0xFFFFFEFA, mappingColor),
      );
    }

    // use this method to changeTheme
    void _changeTheme({required WidgetRef ref, required ThemeData themeData}) {
      Future.delayed(const Duration(milliseconds: 10), () {
        ref.read(appThemeProvider).state = themeData;
      }
    }
}