dawed1999
dawed1999

Reputation: 335

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.

Upvotes: 1

Views: 1536

Answers (2)

dawed1999
dawed1999

Reputation: 335

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;
                });
              },
            ),
          ),
        ],
      ),
    );
  }
}

Upvotes: 1

FMorschel
FMorschel

Reputation: 864

Inside a State class, you almost always have context available. Besides initState, all the other methods can call it (without any downsides, I mean. Inside initState, you can use context as well, but there are some counter-indications for doing that in some cases).

A full explanation of State: https://api.flutter.dev/flutter/widgets/State-class.html

When in doubt, always look for: https://api.flutter.dev and https://api.dart.dev/

Also, a great way of learning new stuff is over Decoding Flutter playlist: https://youtube.com/playlist?list=PLjxrf2q8roU1fRV40Ec8200rX6OuQkmnl

Upvotes: 1

Related Questions