Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter Dynamic Theming

Tags:

flutter

dart

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
    }
like image 247
Jared Avatar asked Feb 19 '19 01:02

Jared


2 Answers

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.

like image 185
Harsh Bhikadia Avatar answered Oct 03 '22 00:10

Harsh Bhikadia


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);
                })
           ),
      ),
    );
  }
}
like image 36
Boris Kayi Avatar answered Oct 03 '22 00:10

Boris Kayi