flutterdartflutter-theme

Updating theme when resuming to Flutter app


I'm new to Flutter and I'm trying to theme my app dynamically so that the user can select his preferred theme either locally or get the system theme.

I managed to get it to work except for when pausing and then resuming to the app after changing the system theme. The theme doesn't update automatically when resuming and I have to run provider.changeToSystemTheme(); by selecting another setting and setting it back for it to update.

I wasn't able to pass final provider = Provider.of<ThemeProvider>(context); in didChangeAppLifecycleState(AppLifecycleState state) in order to run provider.changeToSystemTheme(); since it dosen't have a context to pass into it.

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

enum SingingCharacter {light, dark, systemDefault}
var systemBrightness;


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

class MaterialAppWithTheme extends StatefulWidget {
  const MaterialAppWithTheme({Key? key}) : super(key: key);
  @override
  State<MaterialAppWithTheme> createState() => _MaterialAppWithThemeState();
}

class _MaterialAppWithThemeState extends State<MaterialAppWithTheme> with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);
    switch (state) {
      case AppLifecycleState.resumed:
        break;
      case AppLifecycleState.inactive:
        break;
      case AppLifecycleState.paused:
        break;
      case AppLifecycleState.detached:
        break;
    }
  }

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => ThemeProvider(),
      builder: (context, _) {
        final themeProvider = Provider.of<ThemeProvider>(context);
        return MaterialApp(
          home: NinjaCard2(),
          themeMode: themeProvider.themeMode,
          theme: MyThemes.lightTheme,
          darkTheme: MyThemes.darkTheme,
        );
      }
    );
  }
}


class ThemeProvider extends ChangeNotifier {
  ThemeMode themeMode = ThemeMode.system;

  void changeToLightTheme() {
    themeMode = ThemeMode.light;
    notifyListeners();
  }

  void changeToDarkTheme() {
    themeMode = ThemeMode.dark;
    notifyListeners();
  }

  void changeToSystemTheme() {
    themeMode = ThemeMode.system;
    notifyListeners();
  }
}

class MyThemes{
  static final darkTheme = ThemeData(
    scaffoldBackgroundColor: Colors.black,
    drawerTheme: DrawerThemeData(backgroundColor: MaterialStateColor.resolveWith((states) => Colors.black)),
    radioTheme: RadioThemeData(fillColor: MaterialStateColor.resolveWith((states) => Colors.blue)),
    dividerColor: Colors.white,
    colorScheme: ColorScheme.dark().copyWith(
      primary: Colors.blue,
      onPrimary: Colors.white,
      secondary: Colors.blue,
      onSecondary: Colors.white,
      surface: Color(0xff303031),
    )
  );
  static final lightTheme = ThemeData(
    colorScheme: ColorScheme.light().copyWith(
      primary: Colors.blue,
      onPrimary: Colors.white,
      secondary: Colors.blue,
      onSecondary: Colors.white,
    )
  );
}

class NinjaCard2 extends StatefulWidget {
  const NinjaCard2({Key? key}) : super(key: key);
  @override
  State<NinjaCard2> createState() => _NinjaCard2State();
}

class _NinjaCard2State extends State<NinjaCard2> {
  SingingCharacter? _character = SingingCharacter.systemDefault;
  @override
  Widget build(BuildContext context) {
    final provider = Provider.of<ThemeProvider>(context);
    return Scaffold(
      appBar: AppBar(
        title: const Text('Settings'),
        elevation: 1.5,
        centerTitle: true,
      ),
      body: Column(
        children: <Widget>[
          ListTile(
            title: const Text('Light'),
            leading: Radio<SingingCharacter>(
              value: SingingCharacter.light,
              groupValue: _character,
              onChanged: (SingingCharacter? value) {
                setState(() {
                  _character = value;
                  provider.changeToLightTheme();
                });
              },
            ),
          ),
          ListTile(
            title: const Text('Dark'),
            leading: Radio<SingingCharacter>(
              value: SingingCharacter.dark,
              groupValue: _character,
              onChanged: (SingingCharacter? value) {
                provider.changeToDarkTheme();
                setState(() {
                  _character = value;
                });
              },
            ),
          ),
          ListTile(
            title: const Text('System Default'),
            leading: Radio<SingingCharacter>(
              value: SingingCharacter.systemDefault,
              groupValue: _character,
              onChanged: (SingingCharacter? value) {
                provider.changeToSystemTheme();
                setState(() {
                  _character = value;
                });
              },
            ),
          ),
        ],
      ),
    );
  }
}

Is there any other way to achieve what looking for?

I would also appreciate links to study materials in the answers.


Solution

  • After checking the State documentation, I was able to find the solution.

    All I had to do is add:

    @override
    void didChangeDependencies() {
      super.didChangeDependencies();
    }
    

    after:

    @override
    void initState() {
      super.initState();
    }
    

    Which gives us:

    import 'package:flutter/material.dart';
    import 'package:provider/provider.dart';
    
    enum SingingCharacter {light, dark, systemDefault}
    
    void main() => runApp(MaterialAppWithTheme());
    
    class MaterialAppWithTheme extends StatefulWidget {
      const MaterialAppWithTheme({Key? key}) : super(key: key);
      @override
      State<MaterialAppWithTheme> createState() => _MaterialAppWithThemeState();
    }
    
    class _MaterialAppWithThemeState extends State<MaterialAppWithTheme> with WidgetsBindingObserver {
      @override
      void initState() {
        super.initState();
      }
    
      @override
      void didChangeDependencies() {
        super.didChangeDependencies();
      }
    
      @override
      Widget build(BuildContext context) {
        return ChangeNotifierProvider(
          create: (context) => ThemeProvider(),
          builder: (context, _) {
            final themeProvider = Provider.of<ThemeProvider>(context);
            return MaterialApp(
              home: NinjaCard2(),
              themeMode: themeProvider.themeMode,
              theme: MyThemes.lightTheme,
              darkTheme: MyThemes.darkTheme,
            );
          }
        );
      }
    }
    
    
    class ThemeProvider extends ChangeNotifier {
      ThemeMode themeMode = ThemeMode.system;
    
      void changeToLightTheme() {
        themeMode = ThemeMode.light;
        notifyListeners();
      }
    
      void changeToDarkTheme() {
        themeMode = ThemeMode.dark;
        notifyListeners();
      }
    
      void changeToSystemTheme() {
        themeMode = ThemeMode.system;
        notifyListeners();
      }
    }
    
    class MyThemes{
      static final darkTheme = ThemeData(
        scaffoldBackgroundColor: Colors.black,
        drawerTheme: DrawerThemeData(backgroundColor: MaterialStateColor.resolveWith((states) => Colors.black)),
        radioTheme: RadioThemeData(fillColor: MaterialStateColor.resolveWith((states) => Colors.blue)),
        dividerColor: Colors.white,
        colorScheme: ColorScheme.dark().copyWith(
          primary: Colors.blue,
          onPrimary: Colors.white,
          secondary: Colors.blue,
          onSecondary: Colors.white,
          surface: Color(0xff303031),
        )
      );
      static final lightTheme = ThemeData(
        colorScheme: ColorScheme.light().copyWith(
          primary: Colors.blue,
          onPrimary: Colors.white,
          secondary: Colors.blue,
          onSecondary: Colors.white,
        )
      );
    }
    
    class NinjaCard2 extends StatefulWidget {
      const NinjaCard2({Key? key}) : super(key: key);
      @override
      State<NinjaCard2> createState() => _NinjaCard2State();
    }
    
    class _NinjaCard2State extends State<NinjaCard2> {
      SingingCharacter? _character = SingingCharacter.systemDefault;
      @override
      Widget build(BuildContext context) {
        final provider = Provider.of<ThemeProvider>(context);
        return Scaffold(
          appBar: AppBar(
            title: const Text('Settings'),
            elevation: 1.5,
            centerTitle: true,
          ),
          body: Column(
            children: <Widget>[
              ListTile(
                title: const Text('Light'),
                leading: Radio<SingingCharacter>(
                  value: SingingCharacter.light,
                  groupValue: _character,
                  onChanged: (SingingCharacter? value) {
                    setState(() {
                      _character = value;
                      provider.changeToLightTheme();
                    });
                  },
                ),
              ),
              ListTile(
                title: const Text('Dark'),
                leading: Radio<SingingCharacter>(
                  value: SingingCharacter.dark,
                  groupValue: _character,
                  onChanged: (SingingCharacter? value) {
                    provider.changeToDarkTheme();
                    setState(() {
                      _character = value;
                    });
                  },
                ),
              ),
              ListTile(
                title: const Text('System Default'),
                leading: Radio<SingingCharacter>(
                  value: SingingCharacter.systemDefault,
                  groupValue: _character,
                  onChanged: (SingingCharacter? value) {
                    provider.changeToSystemTheme();
                    setState(() {
                      _character = value;
                    });
                  },
                ),
              ),
            ],
          ),
        );
      }
    }