'Flutter Dynamic Theming

What is the best way to go about dynamically changing the theme of a Flutter app? For example, if the user changes the color to red, I want the theme to instantly be changed to red. I can't find anything very helpful online except one guy said to use the BLOC pattern, which I am not familiar with it. I'd like to hear your guys thoughts on the issue. Thanks!

My current code structure:

var themeData = ThemeData(
    fontFamily: 'Raleway',
    primaryColor: Colors.blue,
    brightness: Brightness.light,
    backgroundColor: Colors.white,
    accentColor: Colors.blue);

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: Constants.appName,
      theme: themeData,
      home: CheckAuth(), //CheckAuth returns MyHomePage usually
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title, @required this.uid}) : super(key: key);

  final String title;
  final String uid;

  @override
  _MyHomePageState createState() => _MyHomePageState();
    }

class _MyHomePageState extends State<MyHomePage> {
    ...build and stuff
    }


Solution 1:[1]

You can use InhertedWidget if you like (instead of BLOC) - Basically it is used to access parent widget anywhere from the tree.

So what you should do is

  1. create InheritedWidget, somewhere in top of tree [from where you want the effect of theme to take place]
  2. wrap it around Theme widget
  3. expose a method to switch theme, by passing the ThemeData you want to replace it with.

Here is some code:

import 'package:flutter/material.dart';

var themeData = ThemeData(
    fontFamily: 'Raleway',
    primaryColor: Colors.blue,
    brightness: Brightness.light,
    backgroundColor: Colors.white,
    accentColor: Colors.blue
);

void main() {
  runApp(
    ThemeSwitcherWidget(
      initialTheme: themeData,
      child: MyApp(),
    ),
  );
}

class ThemeSwitcher extends InheritedWidget {
  final _ThemeSwitcherWidgetState data;

  const ThemeSwitcher({
    Key key,
    @required this.data,
    @required Widget child,
  })  : assert(child != null),
        super(key: key, child: child);

  static _ThemeSwitcherWidgetState of(BuildContext context) {
    return (context. dependOnInheritedWidgetOfExactType(ThemeSwitcher)
            as ThemeSwitcher)
        .data;
  }

  @override
  bool updateShouldNotify(ThemeSwitcher old) {
    return this != old;
  }
}

class ThemeSwitcherWidget extends StatefulWidget {
  final ThemeData initialTheme;
  final Widget child;

  ThemeSwitcherWidget({Key key, this.initialTheme, this.child})
      : assert(initialTheme != null),
        assert(child != null),
        super(key: key);

  @override
  _ThemeSwitcherWidgetState createState() => _ThemeSwitcherWidgetState();
}

class _ThemeSwitcherWidgetState extends State<ThemeSwitcherWidget> {
  ThemeData themeData;

  void switchTheme(ThemeData theme) {
    setState(() {
      themeData = theme;
    });
  }

  @override
  Widget build(BuildContext context) {
    themeData = themeData ?? widget.initialTheme;
    return ThemeSwitcher(
      data: this,
      child: widget.child,
    );
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeSwitcher.of(context).themeData,
      home: CheckAuth(),
    );
  }
}

I have wrapped ThemeSwitcherWidget around MaterialApp so the effect is throughout the app (even when you push new route with Navigator).

Use ThemeSwitcher.of(context).switchTheme(themeData) anywhere below ThemeSwithcerWidget to change the theme.

In question's case it should call ThemeSwitcher.of(context).switchTheme(Theme.of(context).copyWith(primaryColor: Colors.red)) to switch primary color to red throught out the app, for eg. on some button click

EDIT: replaced inheritFromWidgetOfExactType -> dependOnInheritedWidgetOfExactType, since it is deprecated - as pointed by Phoca in comments.

Solution 2:[2]

Using provider package:
theme_changer.dart

var darkTheme = ThemeData.dark();
var lightTheme= ThemeData.light();

class ThemeChanger extends ChangeNotifier {
  ThemeData _themeData;
  ThemeChanger(this._themeData);

  get getTheme => _themeData;
  void setTheme(ThemeData theme) {
    _themeData = theme;
    notifyListeners();
  }
}

main.dart

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => ThemeChanger(lightTheme)),
      ],
      child: MaterialAppWithTheme(),
    );
  }
}

class MaterialAppWithTheme extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final theme = Provider.of<ThemeChanger>(context);
    return MaterialApp(
      theme: theme.getTheme,
      home: FirstScreen(),
    );
  }

first_screen.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import './theme_changer.dart'

class FirstScreen extends StatelessWidget{
  @override
  Widget build(BuildContext context){
    var _themeProvider=Provider.of<ThemeChanger>(context);
    return Scaffold(
      appBar: AppBar(title:Text("First Screen"),),
      body:Container(width:MediaQuery.of(context).size.width,
           height:MediaQuery.of(context).size.height,
           child:Center(
                child:FlatButton(child:Text("Press me"). onPressed:(){
                  _themeProvider.setTheme(_themeProvider.getTheme==lightTheme?darkTheme:lightTheme);
                })
           ),
      ),
    );
  }
}

Solution 3:[3]

This is how to implement the dynamic Theme changing in Your App:

1.You should Change your MyApp into Stateful widget to enable the class to rebuild again when the color changes:

 var _primary = Colors.blue ; // This will hold the value of the app main color

 var themeData = ThemeData(
 fontFamily: 'Raleway',
 primaryColor: _primary, // so when the rebuilds the color changes take effect
 brightness: Brightness.light,
 backgroundColor: Colors.white,
 accentColor: Colors.blue);

 void main() => runApp(new App());

 class App extends StatefulWidget {
 App({Key key,}) :

 super(key: key);

  @override
  _AppState createState() => new _AppState();

  static void setTheme(BuildContext context, Color newColor) {
  _AppState state = context.ancestorStateOfType(TypeMatcher<_AppState>());
   state.setState(() {
     state._primary = newColor;
    });
  }
}

2.The static method setTheme will be the one responsible for color changing :

  class _AppState extends State<App> {

  @override
  Widget build(BuildContext context) {
  return MaterialApp(
  title: Constants.appName,
  theme: themeData,
  home: CheckAuth(), //CheckAuth returns MyHomePage usually
  );
 }
}   

3.When You want to change the theme color from anywhere from your code call this method:

  App.setTheme(context, Colors.blue);  

Solution 4:[4]

I have used get plugin and used Get.changeThemeMode(ThemeMode.(dark/system/light)); it works perfectly for me

First u have to add the get plugin by following the installing guide

then in main change

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
  title: 'Add Your Title',
  debugShowCheckedModeBanner: false,
  theme:_lightTheme,
  darkTheme: _darkTheme,
  home: login(),
  );
 }
}

ON Tap function

import 'package:get/get.dart';


             

  onTap: () {
       Get.changeThemeMode(ThemeMode.dark);
             setState(() async {
                  Navigator.pushReplacement(
                     context,
                     MaterialPageRoute(builder: (BuildContext context) => super.widget));
                });
              }

I have two button for both themes and onTap i have just add the line

Get.changeThemeMode(ThemeMode.dark) for dark mode , Get.changeThemeMode(ThemeMode.dark) for light mode

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 Mazin Ibrahim
Solution 4