Custom theme not working properly. [Flutter]

This is to do with how context and Theme.of work.

From the Theme class source code:

  static ThemeData of(BuildContext context, { bool shadowThemeOnly = false }) {
    final _InheritedTheme inheritedTheme =
        context.inheritFromWidgetOfExactType(_InheritedTheme);
    if (shadowThemeOnly) {
      if (inheritedTheme == null || inheritedTheme.theme.isMaterialAppTheme)
        return null;
      return inheritedTheme.theme.data;
    }

    final ThemeData colorTheme = (inheritedTheme != null) ? inheritedTheme.theme.data : _kFallbackTheme;
    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
    final TextTheme geometryTheme = localizations?.localTextGeometry ?? MaterialTextGeometry.englishLike;
    return ThemeData.localize(colorTheme, geometryTheme);
  }

Theme.of (and Navigator.of(), ....of() etc), look at the context you pass them and then iterate upwards through the tree of widgets looking for a widget of the type specified.

Now, looking at your code

Widget build(BuildContext context) {
    return new MaterialApp(
        theme: _buildDarkTheme(),
        home: new Scaffold(
          appBar: _buildAppBar(),
          body: new Container(
            color: Theme.of(context).accentColor,

you can see that the context you're passing into Theme.of is actually the context above the theme you're creating. So it won't find your theme and will revert to the default. This is because the widget tree looks somewhat like the following (ignoring all the intermediate layers, with the arrow pointing to the context you're using.

MyApp - context <--------
  MaterialApp
    Theme
      Scaffold
        

There are two ways to fix this; the first is to use a Builder class to build your widget within a closure that has the context below the theme. That would look something like this:

class MyApp extends StatelessWidget {
  MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: _buildDarkTheme(),
      home: Scaffold(
        appBar: _buildAppBar(),
        body: Builder(
          builder: (context) => Container(
            color: Theme.of(context).accentColor,
            height: double.infinity,
            child: ListView.builder(...)
          ),
        ),
      ),
    );
  }
}

And it would make a tree that looks somewhat like this:

MyApp - context
  MaterialApp
    Theme
      Scaffold
        Builder - context <---------

The other (preferable) option is to split out the code for your builder into its own class - either a StatelessWidget-inherited class or a StatefulWidget and State pair.


why wouldn't you make a _darkTheme variable like this:

ThemeData _darkTheme = _buildDarkTheme();

and then use it to define to color?

class MyApp extends StatelessWidget {
  MyApp({Key key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        theme: _buildDarkTheme(),
        home: Scaffold(
          appBar: _buildAppBar(),
          body: Container(
            color: _darkTheme.accentColor,
            height: double.infinity,
            child: ListView.builder(...

I had the same problem using themes in the MaterialApp class. If you want to have only one theme for your entire Flutter app, you just have to use one theme in the main page (the one that calls all the other pages).

After doing that, in the other pages, never return the MaterialApp class, otherwise it will overwrite your theme and use the default one.

Here is the code for my main page:

            Widget build(BuildContext context) {

            return MaterialApp(
            theme: ThemeData(
            fontFamily: "Test",
            primaryColor: Colors.yellow,
            buttonColor: Colors.yellow,
             ),
             routes: {

             '/': (BuildContext context) => AuthPage(),
                },
             );

After that, in the AuthPage, return Scaffold and not MaterialApp.

Tags:

Dart

Flutter