Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Notify listener when changing theme mode #15

Closed
friebetill opened this issue Mar 30, 2021 · 6 comments
Closed

Notify listener when changing theme mode #15

friebetill opened this issue Mar 30, 2021 · 6 comments

Comments

@friebetill
Copy link

Describe the bug
Widgets that depend on AdaptiveTheme.of(context) don't rebuild automatically after changing the theme. I guess it is because we are not using an InheritedWidget. The problem is especially annoying for a larger app, where one has to call setState manually everywhere.

Example

import 'package:adaptive_theme/adaptive_theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final savedThemeMode = await AdaptiveTheme.getThemeMode();
  runApp(MyApp(savedThemeMode: savedThemeMode));
}

class MyApp extends StatelessWidget {
  final AdaptiveThemeMode? savedThemeMode;

  const MyApp({Key? key, this.savedThemeMode}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return AdaptiveTheme(
      light: ThemeData(
        brightness: Brightness.light,
        primarySwatch: Colors.red,
        accentColor: Colors.amber,
      ),
      dark: ThemeData(
        brightness: Brightness.dark,
        primarySwatch: Colors.red,
        accentColor: Colors.amber,
      ),
      initial: savedThemeMode ?? AdaptiveThemeMode.light,
      builder: (theme, darkTheme) => MaterialApp(
        title: 'Adaptive Theme Demo',
        theme: theme,
        darkTheme: darkTheme,
        home: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    final theme = AdaptiveTheme.of(context);
    return Scaffold(
      appBar: AppBar(title: Text('Adaptive Theme Demo')),
      body: Center(
        child: TextButton(
          onPressed: () {
            showDialog(context: context, builder: (_) => const ThemeDialog());
          },
          child: Icon(
            theme.mode == AdaptiveThemeMode.light
                ? Icons.wb_sunny_outlined
                : theme.mode == AdaptiveThemeMode.dark
                    ? Icons.bedtime_outlined
                    : Icons.brightness_auto_outlined,
          ),
        ),
      ),
    );
  }
}

class ThemeDialog extends StatefulWidget {
  const ThemeDialog({Key? key}) : super(key: key);

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

class _ThemeDialogState extends State<ThemeDialog> {
  @override
  Widget build(BuildContext context) {
    final adaptiveTheme = AdaptiveTheme.of(context);
    return AlertDialog(
      title: const Text('Theme'),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          RadioListTile<AdaptiveThemeMode>(
            autofocus: true,
            selected: true,
            dense: true,
            title: const Text('Light', style: TextStyle(fontSize: 14)),
            value: AdaptiveThemeMode.light,
            onChanged: (value) => adaptiveTheme.setThemeMode(value!),
            groupValue: adaptiveTheme.mode,
          ),
          RadioListTile<AdaptiveThemeMode>(
            autofocus: true,
            selected: true,
            dense: true,
            title: const Text('Dark', style: TextStyle(fontSize: 14)),
            value: AdaptiveThemeMode.dark,
            onChanged: (value) => adaptiveTheme.setThemeMode(value!),
            groupValue: adaptiveTheme.mode,
          ),
          RadioListTile<AdaptiveThemeMode>(
            autofocus: true,
            selected: true,
            dense: true,
            title: const Text('System', style: TextStyle(fontSize: 14)),
            value: AdaptiveThemeMode.system,
            onChanged: (value) => adaptiveTheme.setThemeMode(value!),
            groupValue: adaptiveTheme.mode,
          ),
        ],
      ),
      actions: <Widget>[
        TextButton(
          onPressed: () => Navigator.of(context).pop(),
          child: Text(
            MaterialLocalizations.of(context).okButtonLabel,
            style: Theme.of(context).primaryTextTheme.button,
          ),
        ),
      ],
    );
  }
}

Expected behavior
I expected that when I change the theme, widget that depend on AdaptiveTheme.of(context) will rebuild itself automatically. But unfortunately this is not the case and I have to call setState manually. This is especially noticeable when I switch between e.g. Light and System mode (and the System mode is Light).

@BirjuVachhani
Copy link
Owner

@friebetill Yeah, that is correct. It won't work because it is not InheritedWidget. However you could use it like this:

Instead of this:

AdaptiveTheme.of(context).mode == AdaptiveThemeMode.dark

do this:

Theme.of(context).brightness == Brightness.light

This works perfectly for me and when I change the theme, it changes for the whole app. Let me know your thoughts on this.

@friebetill
Copy link
Author

This is also problematic if one makes a switch between e.g. light mode and system (Light Mode). In this case the icon (from the example) is not adjusted, because Theme.of(context).brightness does not change.

@BirjuVachhani
Copy link
Owner

BirjuVachhani commented Apr 1, 2021

@friebetill That makes sense!

I guess it wouldn't be a good idea to use InheritedWidget just to satisfy this at this point. It would require a change in the whole library. Instead I could give a ValueNotifier<AdaptiveThemeMode which you can use for your use-case here.

See the example:
import 'package:adaptive_theme/adaptive_theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final savedThemeMode = await AdaptiveTheme.getThemeMode();
  runApp(MyApp(savedThemeMode: savedThemeMode));
}

class MyApp extends StatelessWidget {
  final AdaptiveThemeMode? savedThemeMode;

  const MyApp({Key? key, this.savedThemeMode}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return AdaptiveTheme(
      light: ThemeData(
        brightness: Brightness.light,
        primarySwatch: Colors.red,
        accentColor: Colors.amber,
      ),
      dark: ThemeData(
        brightness: Brightness.dark,
        primarySwatch: Colors.red,
        accentColor: Colors.amber,
      ),
      initial: savedThemeMode ?? AdaptiveThemeMode.light,
      builder: (theme, darkTheme) => MaterialApp(
        title: 'Adaptive Theme Demo',
        theme: theme,
        darkTheme: darkTheme,
        home: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    final theme = AdaptiveTheme.of(context);
    return Scaffold(
      appBar: AppBar(title: Text('Adaptive Theme Demo')),
      body: Center(
        child: TextButton(
          onPressed: () {
            showDialog(context: context, builder: (_) => const ThemeDialog());
          },
          child: Icon(
            theme.mode == AdaptiveThemeMode.light
                ? Icons.wb_sunny_outlined
                : theme.mode == AdaptiveThemeMode.dark
                    ? Icons.bedtime_outlined
                    : Icons.brightness_auto_outlined,
          ),
        ),
      ),
    );
  }
}

class ThemeDialog extends StatefulWidget {
  const ThemeDialog({Key? key}) : super(key: key);

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

class _ThemeDialogState extends State<ThemeDialog> {
  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: const Text('Theme'),
      content: ValueListenableBuilder<AdaptiveThemeMode>(
        valueListenable: AdaptiveTheme.of(context).modeChangeNotifier,
        builder:
            (BuildContext context, AdaptiveThemeMode value, Widget? child) =>
                Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            RadioListTile<AdaptiveThemeMode>(
              autofocus: true,
              selected: true,
              dense: true,
              title: const Text('Light', style: TextStyle(fontSize: 14)),
              value: AdaptiveThemeMode.light,
              onChanged: (value) => adaptiveTheme.setThemeMode(value!),
              groupValue: value,
            ),
            RadioListTile<AdaptiveThemeMode>(
              autofocus: true,
              selected: true,
              dense: true,
              title: const Text('Dark', style: TextStyle(fontSize: 14)),
              value: AdaptiveThemeMode.dark,
              onChanged: (value) => adaptiveTheme.setThemeMode(value!),
              groupValue: value,
            ),
            RadioListTile<AdaptiveThemeMode>(
              autofocus: true,
              selected: true,
              dense: true,
              title: const Text('System', style: TextStyle(fontSize: 14)),
              value: AdaptiveThemeMode.system,
              onChanged: (value) => adaptiveTheme.setThemeMode(value!),
              groupValue: value,
            ),
          ],
        ),
      ),
      actions: <Widget>[
        TextButton(
          onPressed: () => Navigator.of(context).pop(),
          child: Text(
            MaterialLocalizations.of(context).okButtonLabel,
            style: Theme.of(context).primaryTextTheme.button,
          ),
        ),
      ],
    );
  }
}

@friebetill
Copy link
Author

friebetill commented Apr 2, 2021

Yes that would be sufficient for me! :)

I understand if InheritedWidget is too much work, but I guess in the long run it is the better version. (But I haven't looked at all the advantages and disadvantages and specifically why you did your architecture decisions.)

@BirjuVachhani
Copy link
Owner

@friebetill I can add this fix in this weekend. Will update you once published.

@BirjuVachhani
Copy link
Owner

Available in 2.1.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants