Easy to use, customizable and pluggable Theme Provider.

Overview

theme_provider

Easy to use, customizable Theme Provider. This provides app color schemes throughout the app and automatically rebuilds the UI dynamically. You can also persist your color theme as well. Easily store and retrieve user preference without hassle. This package also provides you with several widgets that can help you to easily add theme switching abilities. Additionally you can pass option classes to store and provide data which should be associated with the current theme.

Codemagic build status Pub Package

โ–ถ๏ธ Basic Demonstration

Web demo is available in https://kdsuneraavinash.github.io/theme_provider

Basic Usage Dialog Box
Record Record

๐Ÿ’ป Include in your project

dependencies:
  theme_provider: <latest version>

run packages get and import it

import 'package:theme_provider/theme_provider.dart';

๐Ÿ‘จโ€๐Ÿ’ป Usage

Basic Usage

Wrap your material app like this to use dark theme and light theme out of the box.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ThemeProvider(
      child: ThemeConsumer(
        child: Builder(
          builder: (themeContext) => MaterialApp(
            theme: ThemeProvider.themeOf(themeContext).data,
            title: 'Material App',
            home: HomePage(),
          ),
        ),
      ),
    );
  }
}

Provide additional themes

You may also provide additional themes using the themes parameter. Here you have to provide a theme id string and theme data value. (Make sure to provide unique theme ids)

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ThemeProvider(
      themes: [
        AppTheme.light(), // This is standard light theme (id is default_light_theme)
        AppTheme.dark(), // This is standard dark theme (id is default_dark_theme)
        AppTheme(
          id: "custom_theme", // Id(or name) of the theme(Has to be unique)
          description: "My Custom Theme", // Description of theme
          data: ThemeData(  // Real theme data
            primaryColor: Colors.black,
            accentColor: Colors.red,
          ),
        ),
      ],
      child: ThemeConsumer(
        child: Builder(
          builder: (themeContext) => MaterialApp(
            theme: ThemeProvider.themeOf(themeContext).data,
            title: 'Material App',
            home: HomePage(),
          ),
        ),
      ),
    );
  }
}

Changing and accessing the current theme

You can use the theme id strings to change the current theme of the app.

 ThemeProvider.controllerOf(context).nextTheme();
 // Or
 ThemeProvider.controllerOf(context).setTheme(THEME_ID);

Access current AppTheme

 ThemeProvider.themeOf(context)

Access theme data:

 ThemeProvider.themeOf(context).data
 // or
 Theme.of(context)

Apps with routing

Wrapping material app with ThemeProvider

If you provide the theme consumer on MaterialApp then you don't have to provide ThemeConsumer on routes. However that would disable the ability to use multiple theme controllers. Also a visible flickr may occur at the start of app when the saved theme is loaded.

This approach is much easier to integrate and works well with all other material components such as SearchDelegates and DialogBoxes without wrapping with ThemeConsumers.

Wrapping each route independently with ThemeProvider

However you could also wrap each route and dialog in ThemeConsumer instead of wrapping the whole material app. This will give a more granular control and will not cause a visual flikr. However, some integrations(eg: SearchDelegates) might not be trivial.

MaterialPageRoute(
  builder: (_) => ThemeConsumer(child: SecondPage()),
),

Provide callbacks for theme changing event

If you want to change the StatusBarColor when the theme changes, you can provide a onThemeChanged callback to the ThemeProvider.

Passing Additional Options

This can also be used to pass additional data associated with the theme. Use options to pass additional data that should be associated with the theme. eg: If font color on a specific button changes according to the current theme, create a class to encapsulate the value.

Options classes must implement or extend AppThemeOptions.

  class MyThemeOptions implements AppThemeOptions{
    final Color specificButtonColor;
    MyThemeOptions(this.specificButtonColor);
  }

Then provide the options with the theme.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ThemeProvider(
      themes: [
        AppTheme(
          id: "light_theme",
          description: "Light Theme",
          data: ThemeData.light(),
          options: MyThemeOptions(Colors.blue),
        ),
        AppTheme(
          id: "light_theme",
          description: "Light Theme 2",
          data: ThemeData.dark(),
          options: MyThemeOptions(Colors.red),
        ),
      ],
      // ....
    );
  }
}

Then the option can be retrieved as,

ThemeProvider.optionsOf<MyThemeOptions>(context).specificButtonColor

๐Ÿ’พ Persisting theme

Saving theme

To persist themes, simply pass saveThemesOnChange as true. This will ensure that the theme is saved to the disk.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ThemeProvider(
      saveThemesOnChange: true,
      // ...
    );
  }
}

Or manually save the current theme by just using,

ThemeProvider.controllerOf(context).saveThemeToDisk();

Loading saved theme

defaultThemeId will always be used to determine the initial theme. (If not provided the first theme you specify will be the default app theme.) But you can manually load the previous(saved) theme by using:

 ThemeProvider.controllerOf(context).loadThemeFromDisk();

To load a previously saved theme pass loadThemeOnInit as true:

ThemeProvider(
  saveThemesOnChange: true,
  loadThemeOnInit: true,
  // ...
)

Or to load a theme/do some task at theme controller initialization use onInitCallback. This will get called on the start.

For example, snippet below will load the previously saved theme from the disk. (if previosuly saved.)

ThemeProvider(
  defaultThemeId: "theme_1",
  themes: [
    AppTheme.light(id: "theme_1"),
    AppTheme.light(id: "theme_2"),
    AppTheme.light(id: "theme_3"),
  ],
  saveThemesOnChange: true,
  onInitCallback: (controller, previouslySavedThemeFuture) async {
    // Do some other task here if you need to
    String savedTheme = await previouslySavedThemeFuture;
    if (savedTheme != null) {
      controller.setTheme(savedTheme);
    }
  },
  // ...
)

โšก Dynamically Adding/Removing Themes

Themes can be dynamically added/removed via addTheme and removeTheme. Whether a theme id exists or not can be checked via hasTheme. A theme can only be added if it is not added previously. Similarly, a theme can only be removed if it is previously added. So the membership must be checked before adding/removing themes. (Whether a theme exists or not is decided via its theme id) Note that the active theme cannot be removed.

// Add theme
if (ThemeController.of(context).hasTheme('new_theme')){
  ThemeController.of(context).addTheme(newAppTheme);
}

// Remove theme
if (ThemeController.of(context).hasTheme('new_theme')){
  if (ThemeController.of(context).theme.id != 'new_theme'){
    ThemeController.of(context).removeTheme('new_theme')
  }
}

๐Ÿ”Œ Checking system theme when loading initial theme

You can do this by simply checking for the system theme in onInitCallback callback. Following is an example usage. In the following snippet, the theme will be set to the previously saved theme. If there is no previously saved theme, it is set to light/dark depending on system theme.

You can use any kind of logic here to change the initialization callback.

Dynamically listening to theme changes is not yet available. The theme check is only possible on app start.

import 'package:flutter/scheduler.dart';


ThemeProvider(
  saveThemesOnChange: true, // Auto save any theme change we do
  loadThemeOnInit: false, // Do not load the saved theme(use onInitCallback callback)
  onInitCallback: (controller, previouslySavedThemeFuture) async {
    String savedTheme = await previouslySavedThemeFuture;

    if (savedTheme != null) {
      // If previous theme saved, use saved theme
      controller.setTheme(savedTheme);
    } else {
      // If previous theme not found, use platform default
      Brightness platformBrightness =
          SchedulerBinding.instance.window.platformBrightness;
      if (platformBrightness == Brightness.dark) {
        controller.setTheme('dark');
      } else {
        controller.setTheme('light');
      }
      // Forget the saved theme(which were saved just now by previous lines)
      controller.forgetSavedTheme();
    }
  },
  themes: <AppTheme>[
    AppTheme.light(id: 'light'),
    AppTheme.dark(id: 'dark'),
  ],
  child: ThemeConsumer(
    child: Builder(
      builder: (themeContext) => MaterialApp(
        theme: ThemeProvider.themeOf(themeContext).data,
        title: 'Material App',
        home: HomePage(),
      ),
    ),
  ),
);

๐ŸŽ Additional Widgets

Theme Cycle Widget

IconButton to be added to AppBar to cycle to next theme.

Scaffold(
  appBar: AppBar(
    title: Text("Example App"),
    actions: [CycleThemeIconButton()]
  ),
),

Theme Selecting Dialog

SimpleDialog to let the user select the theme. Many elements in this dialog is customizable. Remember to wrap dialog is a ThemeConsumer.

showDialog(context: context, builder: (_) => ThemeConsumer(child: ThemeDialog()))

โ˜‘๏ธ TODO

  • Add next theme command
  • Add theme cycling widget
  • Add theme selection by theme id
  • Add theme select and preview widget
  • Persist current selected theme
  • Add unit tests and example
  • Remove provider dependency
  • Ids for theme_providers to allow multiple theme providers
  • Add example to demostrate persistence

๐Ÿž Bugs/Requests

If you encounter any problems feel free to open an issue. Pull request are also welcome.

Comments
  • Search Delegate

    Search Delegate

    Currently the ThemeProvider does not work in SearchDelegate, using the Dark theme the background is always white, and the custom font does not work either.

    I've also tried to do:

      @override
      ThemeData appBarTheme(BuildContext context) {
          assert(context != null);
          final ThemeData theme = ThemeProvider.themeOf(context).data;
          assert(theme != null);
    
          return theme;
      }
    

    However it doesn't work.

    Regards.

    opened by mamm26 5
  • Custom themes are not working

    Custom themes are not working

    I added new AppTheme in the themes: property. But it keeps showing default dark() and light() themes. Even I tried to edit default theme constructs with .copywith() method. But it's not even making changes in the default themes. I think AppTheme isn't picking up properties through .copywith() method.

    opened by codesimar 4
  • Use Default System theme when the app launches for the first time

    Use Default System theme when the app launches for the first time

    Thanks for this package . This really helps! I would love to have a feature, taking the system default theme mode when the app launches for the first time.

    opened by vinoopks 3
  • Current theme not being passed down correctly to material widgets

    Current theme not being passed down correctly to material widgets

    I noticed this issue with CircularProgressIndicator, and I've seen it on both the stable and dev channels. But for a lot of the material widgets that should take in the accentColor from theme data, they don't and just revert to the default. Here's a picture of the widget tree if this helps, you can see the CircularProgressIndicator at the bottom of the screenshot of the widget tree:

    Screenshot from 2021-04-28 09-54-55

    And I have the ThemeProvider, ThemeConsumer, and Builder at the root of the tree. Let me know if I'm doing something wrong! Otherwise loving the package, thank you

    bug 
    opened by ahhmino 2
  • Question: Different theme for part of the widget tree

    Question: Different theme for part of the widget tree

    If I wanted to show one rather complex widget in "dark" theme while the rest of the page is "light" (assuming I have only these two themes) how would I do that?

    opened by mploigt 2
  • Automatic theme mode detection

    Automatic theme mode detection

    Hi,

    Thanks a lot for this wonderful package that you made.

    I'm using it for a project and I think that would be really cool if it could use the auto detection, using the system API, to check if the user is using a dark or light theme to set a custom theme.

    Cheers!

    opened by Azzeccagarbugli 2
  • Themes are not applied to MaterialApp

    Themes are not applied to MaterialApp

    I have used this widget tree: ThemeProvider( saveThemesOnChange: true, loadThemeOnInit: true, defaultThemeId: 'light', themes: [...], child: ThemeConsumer( child: MaterialApp(

    And I expect to have this themes configs in my application. In cases where I cant wrap widgets and set proper colors from theme. For example canvas color. Looks like MaterialApp do no affected and themeData not set to it.

    opened by VadPinchuk 2
  • Change theme dynamic

    Change theme dynamic

    Hi! is there a way to add more themes when in other page and use it?

    I have an app that will change the theme when the user select some company and the app will get their color.

    I tried to use the allthemes.add(Mytheme) but i can`t use it.

    opened by brasizza 1
  • Assign me.

    Assign me.

    I saw you have a list of todos on pub. I'd like to contribute but I don't want to start anything you may be in the middle of feel free to assign me something.

    opened by aboyandhis 1
  • The getter 'length' was called on null.

    The getter 'length' was called on null.

    I followed the documentation but I seem to be getting this issue.

    I/flutter (26830): โ•โ•โ•ก EXCEPTION CAUGHT BY WIDGETS LIBRARY โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    I/flutter (26830): The following NoSuchMethodError was thrown building UHungry(dirty):
    I/flutter (26830): The getter 'length' was called on null.
    I/flutter (26830): Receiver: null
    I/flutter (26830): Tried calling: length
    I/flutter (26830): 
    I/flutter (26830): When the exception was thrown, this was the stack:
    I/flutter (26830): #0      Object.noSuchMethod (dart:core-patch/object_patch.dart:50:5)
    I/flutter (26830): #1      new AppTheme (package:theme_provider/src/data/app_theme.dart:84:24)
    I/flutter (26830): #2      defaultMode (package:uHungry/main.dart:65:12)
    I/flutter (26830): #3      UHungry.build (package:uHungry/main.dart:258:29)
    I/flutter (26830): #4      StatelessElement.build (package:flutter/src/widgets/framework.dart:3886:28)
    I/flutter (26830): #5      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3836:15)
    I/flutter (26830): #6      Element.rebuild (package:flutter/src/widgets/framework.dart:3657:5)
    I/flutter (26830): #7      ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:3819:5)
    I/flutter (26830): #8      ComponentElement.mount (package:flutter/src/widgets/framework.dart:3814:5)
    I/flutter (26830): #9      Element.inflateWidget (package:flutter/src/widgets/framework.dart:3053:14)
    I/flutter (26830): #10     Element.updateChild (package:flutter/src/widgets/framework.dart:2856:12)
    I/flutter (26830): #11     RenderObjectToWidgetElement._rebuild (package:flutter/src/widgets/binding.dart:939:16)
    I/flutter (26830): #12     RenderObjectToWidgetElement.mount (package:flutter/src/widgets/binding.dart:910:5)
    I/flutter (26830): #13     RenderObjectToWidgetAdapter.attachToRenderTree.<anonymous closure> (package:flutter/src/widgets/binding.dart:856:17)
    I/flutter (26830): #14     BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2295:19)
    I/flutter (26830): #15     RenderObjectToWidgetAdapter.attachToRenderTree (package:flutter/src/widgets/binding.dart:855:13)
    I/flutter (26830): #16     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&SemanticsBinding&RendererBinding&WidgetsBinding.attachRootWidget (package:flutter/src/widgets/binding.dart:736:7)
    I/flutter (26830): #17     runApp (package:flutter/src/widgets/binding.dart:786:7)
    I/flutter (26830): #18     main.<anonymous closure> (package:uHungry/main.dart:58:9)
    I/flutter (26830): #28     SystemChrome.setPreferredOrientations (package:flutter/src/services/system_chrome.dart)
    I/flutter (26830): #39     OptionalMethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart)
    I/flutter (26830): #50     MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart)
    I/flutter (26830): (elided 43 frames from package dart:async and package dart:async-patch)
    I/flutter (26830): โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    

    This occurs when I try to use my own custom AppTheme

    AppTheme(
          id: "dark_mode",
          data: ThemeData(
              appBarTheme: AppBarTheme(
                color: Color(0xFF1E1E1E),
                elevation: 1.5,
              ),
              scaffoldBackgroundColor: Color(0xFF121212),
              backgroundColor: Color(0xFF1E1E1E),
              dialogBackgroundColor: Color(0xFF121212),
              bottomSheetTheme: BottomSheetThemeData(
                  backgroundColor: Color(0xFF121212),
                  elevation: 0,
                  shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.only(
                        topLeft: Radius.circular(40),
                        topRight: Radius.circular(40),
                      )
                  )
              ),
              textTheme: TextTheme(
                body1: TextStyle(
                    color: Color(0xFFFFFFFF)
                ),
                body2: TextStyle(
                    color: Color(0xFF8A8A8A)
                ),
                title: TextStyle(
                    color: Color(0xFFFFFFFF)
                ),
                subtitle: TextStyle(
                    color: Color(0xFF8A8A8A)
                ),
                caption: TextStyle(
                    color: Color(0xFF8A8A8A)
                ),
                display1: TextStyle(
                    color: Color(0xFFF05A22)
                ),
                display2: TextStyle(
                    color: Color(0xFFFFFFFF)
                ),
                display3: TextStyle(
                    color: Color(0xFF1E1E1E)
                ),
              ),
              cardColor: Color(0xFF1E1E1E),
              splashColor: Colors.transparent,
              highlightColor: Colors.transparent,
              inputDecorationTheme: InputDecorationTheme(
                  filled: true,
                  fillColor: Colors.transparent,
                  enabledBorder: OutlineInputBorder(
                      borderSide: BorderSide(
                          color: Color(0xFF8A8A8A)
                      ),
                      borderRadius: BorderRadius.circular(30.0)
                  ),
                  focusedBorder: OutlineInputBorder(
                    borderSide: BorderSide(
                        color: Color(0xFFF05A22)
                    ),
                    borderRadius: BorderRadius.circular(30.0),
                  ),
                  errorBorder: OutlineInputBorder(
                      borderSide: BorderSide(
                          color: Color(0xFFCE020D)
                      ),
                      borderRadius: BorderRadius.circular(30.0)
                  ),
                  focusedErrorBorder: OutlineInputBorder(
                      borderSide: BorderSide(
                          color: Color(0xFFCE020D)
                      ),
                      borderRadius: BorderRadius.circular(30.0)
                  )
              )
          )
      )
    
    opened by mjhansen3 1
  • cursorColor not work

    cursorColor not work

    ` class AppThemeUtils extends AppTheme { static AppTheme lightTheme() { return AppTheme( id: 'light_theme',

         data: ThemeData(
        
           /// ๅ…‰ๆ ‡้ขœ่‰ฒ
           cursorColor: HexColor('#262626'),
    
         ),
       );
     }
    

    } `

    opened by iptodays 0
  • Including system default theme in the list

    Including system default theme in the list

    Congratulations, This is great package.

    Iยดm trying to create a dialog in which user gets 3 option:

    • System Default;
    • Light Theme;
    • Dark Theme;

    Where the system default I believe you know it get the device predefined theme. Can you help me achieving #this. Thanks in advance.

    opened by achamene 2
  • Choosing light or dark theme based on ThemeMode

    Choosing light or dark theme based on ThemeMode

    I just bumped into your library while looking for references on how to properly get MaterialApp to be a consumer of a ChangeNotifyProvider that is providing theme data.

    It looks like you've already written a lot of the same code I have, and I'm looking at switching over to your library instead of my custom code.

    One thing I've noticed is different with your implementation is that you don't currently make any use of Material's ThemeMode. This enum has 3 values, system, light, and dark. The default for MaterialApp is system. If the system is set to use a dark theme (i.e Android system preferences, or the Browser theme default) then MaterialApp will attempt to use the ThemeData provided in darkTheme. If that property is not set, then it will default to theme.

    What this allows is to have an overall theme but apply a light and dark variant of it, and set both of them in the MaterialApp properties theme and darkTheme, then either let the system default choose, or if the user wishes to override, the way that override is implemented is by changing the themeMode property to either ThemeMode.light or ThemeMode.dark.

    Here is an example of my AppTheme model that can use this. Would you be interested in changes to your library to try to take ThemeMode into account?

    import 'package:flutter/material.dart';
    
    class AppThemeModel extends ChangeNotifier {
      var _appTheme = defaultAppTheme;
    
      AppTheme get appTheme => _appTheme;
    
      set appTheme(AppTheme appTheme) {
        _appTheme = appTheme;
        notifyListeners();
      }
    }
    
    final defaultAppTheme = AppTheme._('System Default', ThemeMode.system, ThemeData.light(), ThemeData.dark());
    final appThemes = <String, AppTheme>{
      'SystemDefault': defaultAppTheme,
      'BasicLight': AppTheme._('Basic Light', ThemeMode.light, ThemeData.light(), null),
      'BasicDark': AppTheme._('Basic Dark', ThemeMode.dark, null, ThemeData.dark()),
      'Fortnightly': AppTheme._('Fortnightly', ThemeMode.light, _buildFortnightlyTheme(), null),
      'Fortnightly2': AppTheme._('Fortnightly2', ThemeMode.light, _buildFortnightlyTheme(), null),
    };
    
    class AppTheme {
      final String _name;
      final ThemeMode _mode;
      final ThemeData? _lightData;
      final ThemeData? _darkData;
    
      const AppTheme._(this._name, this._mode, this._lightData, this._darkData);
    
      String get name => _name;
    
      ThemeMode get themeMode => _mode;
    
      ThemeData? get lightThemeData => _lightData;
    
      ThemeData? get darkThemeData => _darkData;
    
      @override
      String toString() {
        return _name;
      }
    }
    
    ThemeData _buildFortnightlyTheme() {
      TextTheme _buildTextTheme(TextTheme base) {
        TextTheme theme = base.apply(bodyColor: Colors.black, displayColor: Colors.black);
    
        theme = theme.copyWith(
          headline2: base.headline2!.copyWith(
            fontFamily: 'LibreFranklin',
            fontSize: 18,
            fontWeight: FontWeight.w500,
            color: Colors.black.withAlpha(153),
          ),
          headline4: base.headline4!.copyWith(
            fontFamily: 'Merriweather',
            fontStyle: FontStyle.italic,
            fontSize: 28,
            fontWeight: FontWeight.w800,
            color: Colors.black,
            height: .88,
          ),
          headline5: base.headline5!.copyWith(fontWeight: FontWeight.w500),
          bodyText1: base.bodyText1!.copyWith(
            fontFamily: 'Merriweather',
            fontSize: 16,
            fontWeight: FontWeight.w300,
            color: const Color(0xFF666666),
            height: 1.4,
            letterSpacing: .25,
          ),
          bodyText2: base.bodyText2!.copyWith(
            fontFamily: 'Merriweather',
            fontSize: 14,
            fontWeight: FontWeight.w300,
            color: const Color(0xFF666666),
            height: 1.11,
          ),
          overline: const TextStyle(
            fontFamily: 'LibreFranklin',
            fontSize: 10,
            fontWeight: FontWeight.w700,
            color: Colors.black,
          ),
        );
    
        return theme;
      }
    
      final ThemeData base = ThemeData.light();
      return base.copyWith(
        primaryTextTheme: _buildTextTheme(base.primaryTextTheme),
        scaffoldBackgroundColor: Colors.white,
      );
    }
    
    opened by deinspanjer 2
  • Changing the theme rebuilds app with a custom navigator

    Changing the theme rebuilds app with a custom navigator

    I'm using a custom navigator in my flutter app, and it seems to not play well with this plugin - whenever a theme is switched (using either nextTheme() or setThemeId(themeId), the app reverts to the initial route of the navigator and the initial state of that route. No state or route information is saved. Is there any workaround for this? Using flutter 2.2.1

    I have two reproducible examples, one with a custom navigator and one without:

    With custom navigator:

    import 'package:flutter/material.dart';
    import 'package:theme_provider/theme_provider.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return ThemeProvider(
          child: ThemeConsumer(
            child: Builder(
              builder: (context) => MaterialApp(
                title: 'Flutter Demo',
                theme: ThemeProvider.themeOf(context).data,
                home: MainNavigator(),
              ),
            ),
          ),
        );
      }
    }
    
    class MyCounterPage extends StatefulWidget {
      MyCounterPage({Key? key, required this.title}) : super(key: key);
      final String title;
    
      @override
      _MyCounterPageState createState() => _MyCounterPageState();
    }
    
    class _MyCounterPageState extends State<MyCounterPage> {
      int _counter = 0;
    
      void _incrementCounter() {
        setState(() {
          _counter++;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  'You have pushed the FAB this many times:',
                ),
                Text(
                  '$_counter',
                  style: Theme.of(context).textTheme.headline4,
                ),
                TextButton(
                    onPressed: () => Navigator.of(context).pushNamed("second"),
                    child: Text("Tap here to go to the theme page"))
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ),
        );
      }
    }
    
    class MainNavigator extends StatelessWidget {
    
      MainNavigator({Key? key}) : super(key: key);
    
      final GlobalKey<NavigatorState> navigatorKey = GlobalKey();
    
      @override
      Widget build(BuildContext context) {
        return WillPopScope(
            onWillPop: () async {
              return false;
            },
            child: Navigator(
              key: navigatorKey,
              initialRoute: "first",
              onGenerateRoute: (settings) {
                if(settings.name == "first") {
                  return MaterialPageRoute(builder: (context) =>
                      MyCounterPage(title: 'Flutter Demo Home Page'),
                      settings: settings);
                }
                return MaterialPageRoute(builder: (context) =>
                    MyThemePage(title: 'Flutter Demo Home Page'),
                    settings: settings);
              },
            )
        );
      }
    }
    
    class MyThemePage extends StatefulWidget {
      MyThemePage({Key? key, required this.title}) : super(key: key);
      final String title;
    
      @override
      _MyThemePageState createState() => _MyThemePageState();
    }
    
    class _MyThemePageState extends State<MyThemePage> {
      int _counter = 0;
    
      void _incrementCounter() {
        ThemeProvider.controllerOf(context).nextTheme();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  'Press the FAB to change the theme',
                ),
                Text(
                  '$_counter',
                  style: Theme.of(context).textTheme.headline4,
                ),
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ),
        );
      }
    }
    

    Without custom navigator (using default routes in root material app):

    import 'package:flutter/material.dart';
    import 'package:theme_provider/theme_provider.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return ThemeProvider(
          child: ThemeConsumer(
            child: Builder(
              builder: (context) => MaterialApp(
                title: 'Flutter Demo',
                theme: ThemeProvider.themeOf(context).data,
                initialRoute: "first",
                onGenerateRoute: (settings) {
                  if(settings.name == "first") {
                    return MaterialPageRoute(builder: (context) =>
                        MyCounterPage(title: 'Flutter Demo Home Page'),
                        settings: settings);
                  }
                  return MaterialPageRoute(builder: (context) =>
                      MyThemePage(title: 'Flutter Demo Home Page'),
                      settings: settings);
                },
              ),
            ),
          ),
        );
      }
    }
    
    class MyCounterPage extends StatefulWidget {
      MyCounterPage({Key? key, required this.title}) : super(key: key);
      final String title;
    
      @override
      _MyCounterPageState createState() => _MyCounterPageState();
    }
    
    class _MyCounterPageState extends State<MyCounterPage> {
      int _counter = 0;
    
      void _incrementCounter() {
        setState(() {
          _counter++;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  'You have pushed the FAB this many times:',
                ),
                Text(
                  '$_counter',
                  style: Theme.of(context).textTheme.headline4,
                ),
                TextButton(
                    onPressed: () => Navigator.of(context).pushNamed("second"),
                    child: Text("Tap here to go to the theme page"))
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ),
        );
      }
    }
    
    class MyThemePage extends StatefulWidget {
      MyThemePage({Key? key, required this.title}) : super(key: key);
      final String title;
    
      @override
      _MyThemePageState createState() => _MyThemePageState();
    }
    
    class _MyThemePageState extends State<MyThemePage> {
      int _counter = 0;
    
      void _incrementCounter() {
        ThemeProvider.controllerOf(context).nextTheme();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  'Press the FAB to change the theme',
                ),
                Text(
                  '$_counter',
                  style: Theme.of(context).textTheme.headline4,
                ),
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ),
        );
      }
    }
    
    opened by ahhmino 0
  • onInitCallback called every time app moved to the background and back

    onInitCallback called every time app moved to the background and back

    Seems like onInitCallback called every time when the application moved to the background and back. Every time there is an animation of changing theme from light to dark.

    My build method:

    return ThemeProvider(
          saveThemesOnChange: true,
          themes: [
            AppTheme(
              id: 'light',
              description: 'Light',
              data: _theme
            ),
            AppTheme(
              id: 'dark',
              description: 'Dark',
              data: _themeDark
            ),
          ],
          onInitCallback: _onThemesInitCallback,
          child: ThemeConsumer(
            child: Builder(
              builder: (themeContext) => MaterialApp(
                  ...
              ),
            ),
          ),
        );
    

    And _onThemesInitCallback method:

      void _onThemesInitCallback(ThemeController controller, previouslySavedThemeFuture) async {
        print("_onThemesInitCallback ${controller.currentThemeId}");
        String? savedTheme = await previouslySavedThemeFuture;
        print("savedTheme $savedTheme");
        if (savedTheme != null) {
          controller.setTheme(savedTheme);
        } else {
          // If previous theme not found, use platform default
          Brightness platformBrightness = SchedulerBinding.instance!.window.platformBrightness;
          if (platformBrightness == Brightness.dark) {
            controller.setTheme('dark');
          } else {
            controller.setTheme('light');
          }
          // Forget the saved theme(which were saved just now by previous lines)
          controller.forgetSavedTheme();
        }
        print("_onThemesInitCallback END ${controller.currentThemeId}");
      }
    

    Also, I have prints on the application moved to the background and foreground (AppLifecycleState.resumed and .paused) What I see in the logs during the test on the video:

    I/flutter ( 5434): Application is on the foreground
    I/flutter ( 5434): _onThemesInitCallback light
    I/flutter ( 5434): savedTheme dark
    I/flutter ( 5434): _onThemesInitCallback END dark
    I/flutter ( 5434): _onThemesInitCallback light
    I/flutter ( 5434): savedTheme dark
    I/flutter ( 5434): _onThemesInitCallback END dark
    I/flutter ( 5434): Application is on the background
    I/flutter ( 5434): _onThemesInitCallback light
    I/flutter ( 5434): savedTheme dark
    I/flutter ( 5434): _onThemesInitCallback END dark
    

    Video: https://youtu.be/79vcS8sgYys

    Tried to fix it by myself in my application, but seems like something strange in the package. Thanks a lot for the package and your awesome work!

    opened by narritt 2
Owner
K.D. Sunera Avinash Chandrasiri
K.D. Sunera Avinash Chandrasiri
A customizable and easy to use UI widgets to help develop apps faster

Lenore UI Beautiful, customizable and easy to use UI widgets to help me (or maybe you) develop my (or maybe your) apps faster. This is my personal pac

Lenore 3 Nov 4, 2022
โœจA clean and lightweight loading/toast widget for Flutter, easy to use without context, support iOSใ€Android and Web

Flutter EasyLoading English | ็ฎ€ไฝ“ไธญๆ–‡ Live Preview ?? https://nslog11.github.io/flutter_easyloading Installing Add this to your package's pubspec.yaml fi

nslog11 1k Jan 9, 2023
Flexible and easy to use page transitions.

flutter_villains What are heroes without villains? (Profile image from: https://unsplash.com/photos/pAs4IM6OGWI) Check out the article. What are villa

Norbert Kozsir 356 Dec 12, 2022
An animation Framework for Flutter. It is state-based, declarative, extensible, composable and easy to use.

โš ๏ธ This package is in an early state of development. If you find any bugs or have any suggestions, please open an issue. Fleet is an animation framewo

Gabriel Terwesten 3 Sep 29, 2022
Flutter animated theme switcher

animated_theme_switcher Animated theme switcher. This library starts from Peyman's stackoverflow question how-to-add-animation-for-theme-switching-in-

Kherel 223 Dec 23, 2022
An easy way to use AnimationController with Curve.

CurvedAnimationController An easy way to use AnimationController with Curve. Getting Started Add dependency in your flutter project. $ flutter pub add

Salman S 6 Nov 23, 2021
A Flutter package with a selection of simple yet very customizable set of loading animations.

Flutter Loading Animations A simple yet very customizable set of loading animations for Flutter projects. Installation Add the following to your pubsp

Andre Cytryn 171 Sep 23, 2022
Highly customizable, feature-packed calendar widget for Flutter

TableCalendar Highly customizable, feature-packed calendar widget for Flutter. TableCalendar with custom styles TableCalendar with custom builders Fea

Aleksander Woลบniak 1.5k Jan 7, 2023
Progress State Button - A customizable progress button for Flutter

Progress State Button - A customizable progress button for Flutter

Selim 108 Dec 12, 2022
๐Ÿ”ฅ๐Ÿ”ฅ๐Ÿ”ฅ Easy to build an animation set

โœจ Flutter Animation Set [Lanuage ~~] English | ไธญๆ–‡ๆ–‡ๆกฃ Simplified Flutter stagger animation.To drive the Flutter stagger animation through a timeline in

YYDev 271 Oct 31, 2022
๐Ÿ”ฅ๐Ÿ”ฅ๐Ÿ”ฅ Easy to build an animation set

โœจ Flutter Animation Set [Lanuage ~~] English | ไธญๆ–‡ๆ–‡ๆกฃ Simplified Flutter stagger animation.To drive the Flutter stagger animation through a timeline in

YYDev 271 Oct 31, 2022
IntroAnimationSlider - A simple Flutte Animation Introduction for Mobile app easy to implement Using intro Views flutter

introappanimation simple Flutte Animation Introduction for Mobile app easy to im

null 3 Sep 22, 2022
This repository demonstrates use of various widgets in flutter and tricks to create beautiful UI elements in flutter for Android and IOS

AwesomeFlutterUI The purpose of this repository is to demonstrate the use of different widgets and tricks in flutter and how to use them in your proje

Subir Chakraborty 132 Nov 13, 2022
A collection of Screens and attractive UIs built with Flutter ready to be used in your applications. No external libraries are used. Just download, add to your project and use.

Flutter Screens A collection of Login Screens, Buttons, Loaders and Widgets with attractive UIs, built with Flutter, ready to be used in your applicat

Samarth Agarwal 5k Dec 31, 2022
Create powerful animations in Flutter and use the hero animation for complex animations

Hero Animation - Locations UI - Flutter Create powerful animations in Flutter and use the hero animation for complex animations. โšก โ€‚Social Media โ€‚Twit

null 3 Dec 11, 2021
Simple to use yet powerfull style widgets with syntax inspired by CSS.

Division Simple to use yet powerfull style widgets with syntax inspired by CSS. Please check out styled_widget which is a replacement of Division! The

Rein Gundersen Bentdal 266 Nov 20, 2022
A fresh and modern Google Contacts manager that integrates with GitHub and Twitter.

Flokk A fresh and modern Google Contacts manager that integrates with GitHub and Twitter. Demo Builds Web: https://flokk.app Linux: https://snapcraft.

gskinner team 1.3k Jan 3, 2023
A flutter package which contains a collection of some cool and beautiful effects; support android and ios

flutter effects A flutter package which contains a collection of some cool and beautiful effects; support android and ios . Screenshot type support ch

ๅคงๆตท่ฑš 462 Jan 3, 2023
๐Ÿ”” A flutter package to create cool and beautiful text animations. [Flutter Favorite Package]

Animated Text Kit A flutter package which contains a collection of some cool and awesome text animations. Recommended package for text animations in C

Ayush Agarwal 1.4k Jan 6, 2023