Adrian Avram
Adrian Avram

Reputation: 957

Custom theme not working properly. [Flutter]

I have created the following theme for my app:

ThemeData _buildDarkTheme() {
  final baseTheme = ThemeData(fontFamily: "Sunflower",);
  return baseTheme.copyWith(
      brightness: Brightness.dark,
      primaryColor: Colors.grey[800],
      accentColor: Colors.grey[850]);
}

I then apply it to my app as follows:

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

However, when I try to access the accent color inside the container (or anywhere else) instead of it being the expected, Colors.grey[850], it instead defaults to blue. Also, trying to use the custom font Sunflower font family does not work, but when I instead use

new Text("Hello World", style: new TextStyle(fontFamily: "Sunflower"))

The font appears correctly.

I am new to flutter and dart so any help resolving these issues would be appreciated.

Upvotes: 14

Views: 24605

Answers (3)

rmtmckenzie
rmtmckenzie

Reputation: 40503

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.

Upvotes: 32

Riad Rekab
Riad Rekab

Reputation: 129

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.

Upvotes: 4

CakeL
CakeL

Reputation: 594

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(...

Upvotes: 2

Related Questions