Skip to content

AppBar doesn't apply overriden Theme #50606

Description

@feinstein

I have a very simple AppBar outside of a Scaffold, like:

class MyScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: <Widget>[
          AppBar(
            title: Text('hi'),
            flexibleSpace: Container(color: Colors.purple, height: 200,),
          ),
          Expanded(
            child: Center(
              child: Text('hi again'),
            ),
          ),
        ],
      ),
    );
  }
}

Which renders like:

image

The MaterialApp theme primaryColorBrightness is Brightness.light.

Now, if I change the MaterialApp theme primaryColorBrightness to Brightness.dark the status bar color AND the AppBar icons and text change to white:

image

But if I just want to change the Theme for MyScreen and not the whole app, the status bar changes color to white, but the AppBar doesn't:

class MyScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Theme(
      data: Theme.of(context).copyWith(primaryColorBrightness: Brightness.dark),
      child: Scaffold(
        body: Column(
          children: <Widget>[
            AppBar(
              title: Text('hi'),
              flexibleSpace: Container(color: Colors.purple, height: 200,),
            ),
            Expanded(
              child: Center(
                child: Text('hi again'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

As can be seen here:

image

This inconsistency seems like a bug to me.

Activity

  1. added
    p: material_uimaterial_ui package in flutter/packages
    frameworkflutter/packages/flutter repository. See also f: labels.
    on Feb 12, 2020
  2. TahaTesser commented on Apr 8, 2020

    @TahaTesser
    Contributor

    Issue exist

    flutter doctor -v
    [✓] Flutter (Channel dev, v1.18.0, on Mac OS X 10.15.4 19E266, locale en-GB)
        • Flutter version 1.18.0 at /Users/taha/Code/flutter_dev
        • Framework revision 8f7327f83a (31 hours ago), 2020-04-06 22:11:01 -0400
        • Engine revision 49891e0653
        • Dart version 2.8.0 (build 2.8.0-dev.20.0 1210d27678)
    
     
    [✓] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
        • Android SDK at /Users/taha/Code/sdk
        • Platform android-29, build-tools 29.0.3
        • ANDROID_HOME = /Users/taha/Code/sdk
        • Java binary at: /Applications/Android
          Studio.app/Contents/jre/jdk/Contents/Home/bin/java
        • Java version OpenJDK Runtime Environment (build
          1.8.0_212-release-1586-b4-5784211)
        • All Android licenses accepted.
    
    [✓] Xcode - develop for iOS and macOS (Xcode 11.4)
        • Xcode at /Applications/Xcode.app/Contents/Developer
        • Xcode 11.4, Build version 11E146
        • CocoaPods version 1.9.1
    
    [✓] Chrome - develop for the web
        • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
    
    [✓] Android Studio (version 3.6)
        • Android Studio at /Applications/Android Studio.app/Contents
        • Flutter plugin version 45.0.1
        • Dart plugin version 192.7761
        • Java version OpenJDK Runtime Environment (build
          1.8.0_212-release-1586-b4-5784211)
    
    [✓] VS Code (version 1.43.2)
        • VS Code at /Applications/Visual Studio Code.app/Contents
        • Flutter extension version 3.9.1
    
    [✓] Connected device (4 available)
        • SM M305F   • 32003c30dc19668f • android-arm64  • Android 10 (API 29)
        • macOS      • macOS            • darwin-x64     • Mac OS X 10.15.4 19E266
        • Chrome     • chrome           • web-javascript • Google Chrome
          80.0.3987.149
        • Web Server • web-server       • web-javascript • Flutter Tools
    
    • No issues found!
    
  3. added
    has reproducible stepsThe issue has been confirmed reproducible and is ready to work on
    a: qualityA truly polished experience
    on Apr 8, 2020
  4. rydmike commented on Jul 22, 2020

    @rydmike
    Contributor

    AppBar Theme Issue

    I would like to add that the challanges with the AppBar theme go a bit further than just the issue with its brightness being tied to the brightness of the primaryColor of the overall theme. Here are my findings:

    1. AppBar Brightness has no function.
    2. Difficult to create a light AppBar theme when primaryColor is of Brightness.dark.

    Applies to:

    All Flutter platform versions and channels, going back at least 1.5 years.

    Workaround

    EDIT: While making this issue comment I towards the end figured out that nr 2 above is just a consequence of no geometry being available at the point when the app bar text theme is being made. More about this in the conclusion at the end.
    I left all the written reasoning of how I got there intact, as I think it might help others struggling with the AppBarTheme understand what is going on.

    1. AppBar Brightness has no function

    The AppBar theme's brightness property has no function or relevance, it has never done anything useful.
    I agree with the finding in the related issue #61618 that if you do a

    theme: ThemeData(
      appBarTheme: const AppBarTheme(
        brightness: Brightness.light),
      ),

    It is then a fair assumption that it would force dark text and and dark icon theme if you set brightness: Brightness.light, if icon and text properties are not specified by the appbar theme itself, and vise versa for Brightness.dark. However, it does not do anything useful, other than carry the info if set correctly in relation to the color property of the AppBarTheme.

    2. Difficult to create a light AppBar theme when primaryColor is of Brightness.dark

    The header is a bit short and needs elaboration. The coupling of the AppBar's text theme to the overall primaryTextColor theme is so tight that it is actually very difficult to get around it. It's bond is so strongly that it is currently impossible to create an AppBarTheme that goes against this coupling without breaking some parts of it.

    For example it is impossible to create an AppBar theme that is light (usually then the popular white appbar) and need black typography, when the overall theme is light and its primaryColor is of Brightness.dark, thus creating an overall theme where the primaryTextTheme gets white typography.

    Below some examples of what one might try when trying to make a light or often white app bar. I'm just using grey in the examples so both white and black text can be seen on the appbar.

    2.1 Demo of AppBar's coupling to primaryColor and primaryTextTheme

    Lets start with:

    theme: ThemeData(
      appBarTheme: AppBarTheme(
        color: Colors.grey[300],
        brightness: Brightness.light,
        textTheme: const TextTheme(),
        iconTheme: const IconThemeData(color: Colors.black87),
        actionsIconTheme: const IconThemeData(color: Colors.black87),
        elevation: 0),
      ),

    It will result in an appbar like this, which is surprising, well it is if you expected that the brightness setting should give you dark text:
    image

    Now let's try to make the text dark manually by changing the AppBar's TextTheme, first we try this:

    theme: ThemeData(
      appBarTheme: AppBarTheme(
        color: Colors.grey[300],
        brightness: Brightness.light,
        textTheme: Typography.material2018().black,
        iconTheme: const IconThemeData(color: Colors.black87),
        actionsIconTheme: const IconThemeData(color: Colors.black87),
        elevation: 0),
      ),

    image

    and second try this this variant:

    theme: ThemeData(
      appBarTheme: AppBarTheme(
        color: Colors.grey[300],
        brightness: Brightness.light,
        textTheme: const TextTheme()
            .merge(Typography.material2014(platform: TargetPlatform.android).black),
        iconTheme: const IconThemeData(color: Colors.black87),
        actionsIconTheme: const IconThemeData(color: Colors.black87),
        elevation: 0),
      ),

    image

    In both above cases we got black text, but the correct text styles are missing, all we get is a small default text style.

    Let's try one more with the apply method.

    theme: ThemeData(
      appBarTheme: AppBarTheme(
        color: Colors.grey[300],
        brightness: Brightness.light,
        textTheme: const TextTheme()
            .apply(bodyColor: Colors.black, displayColor: Colors.black87),
        iconTheme: const IconThemeData(color: Colors.black87),
        actionsIconTheme: const IconThemeData(color: Colors.black87),
        elevation: 0),
      ),

    Well the style is OK, but the apply black color specification did not have any impact at all, which was surprising, we got white text but the style is OK.
    image

    The above examples can be found in this CodePen: https://codepen.io/rydmike/pen/RwrEodX
    You just have to swap comment lines for the AppBarTheme's textTheme property to try them all.

    Seems tricky to do this, but let's dig deeper and try this. We create a ThemeData object first, apply its default text theme to the AppBar's text theme.

    // Create a default light theme
    final ThemeData themeBase = ThemeData();
    // Store it's default text theme, it is already black for the light surface
    final TextTheme blackTextTheme = themeBase.textTheme;
    // Let's try to use this in a ThemeData Object and use it as a Theme
    final ThemeData theme = ThemeData(
      appBarTheme: AppBarTheme(
        color: Colors.grey[300],
        brightness: Brightness.light,
        textTheme: blackTextTheme,
        iconTheme: const IconThemeData(color: Colors.black87),
        actionsIconTheme: const IconThemeData(color: Colors.black87),
        elevation: 0),
    );

    Result is this:
    image

    That was surprising and it seemed odd, so I also printed out a text with the copied blackTextTheme and that gave me the clue that there is no geometry yet when we copy the theme. The geometry is applied in the localize process in the MaterialApp and apparently that is what is wrong with the text theme in all the other cases too.

    In this last case it obvious that it does not know about the geometry yet via Typography localisations, but it was a bit less obvious in the first CodePen when the ThemeData was constructed in the MaterialApp's theme property. It is easy to assume it will get correct geometry at this point, but the situation is of course the same, it does not yet have any geometry here either and when you make an AppBar theme yourself, it never gets any geometry applied during the localize process, it does not get applied to the custom AppBarTheme.

    I noticed after some digging in the source code (always pays to do that, it is fabulously well documented) that it is the static method ThemeData.localize that actually applies the text style geometry and this is done to the textTeme, primaryTextTheme and accentTextTheme themes. Furthermore the call to do this typography geometry localization, is done by the Theme.of static method, which of course needs a build context for the inherited theme widget. So basically we cannot easily make a text style for any theme with geometry that that should also depend on localized Typography, before we have a context. Well that was interesting and raises some new questions.

    In any case, now that we figured this out, we can of course apply englishLike geometry to see if we can get it to look right. First we test to fix the above example:

    // Create a default light theme
    final ThemeData themeBase = ThemeData();
    // Store it's default text theme, it is already black for the light surface
    final TextTheme blackTextTheme = themeBase.textTheme.merge(Typography.englishLike2018);
    final ThemeData theme = ThemeData(
      appBarTheme: AppBarTheme(
        color: Colors.grey[300],
        brightness: Brightness.light,
        textTheme: blackTextTheme,
        iconTheme: const IconThemeData(color: Colors.black87),
        actionsIconTheme: const IconThemeData(color: Colors.black87),
        elevation: 0),
    );

    And finally we get an AppBar with the correct style and color.
    image

    The CodePen for the above example can be found here: https://codepen.io/rydmike/pen/PoZVNxr


    Now we can also apply this workaround solution for the original challenge to make a white AppBar, for a light theme, with a dark primary color, like so:

    MaterialApp(
          debugShowCheckedModeBanner: false,
          title: 'Flutter Demo',
          theme: ThemeData(
            primaryColor: Colors.indigo[800],
            scaffoldBackgroundColor: Colors.white,
            appBarTheme: AppBarTheme(
              color: Colors.white,
              brightness: Brightness.light,
              textTheme:
                  Typography.material2018().black.merge(Typography.englishLike2018),
              iconTheme: const IconThemeData(color: Colors.black87),
              actionsIconTheme: const IconThemeData(color: Colors.black87),
              elevation: 0,
            ),
          ),
          home: MyHomePage(title: 'Light AppBar Theme Issue #50606'),
        );
    

    The result:

    image


    Conclusion: Remaining issue and why I gave it a "Workaround" status

    Apart from being very complex to figure out how to get the desired end result, creating an AppBar theme this way results in one that will not get typography localization's applied to it as a apart of the localization process, so now you have to deal with that part yourself in the app. This is thus not really a working solution for an international app. How can we get around this? I have to experiment with that a bit, have some ideas, but I need to experiment more with it.

    --

    A white app bar, in a light theme is a very popular design, focus remains more on the content. The design trend started on iOS, but has spread to Android and Material too. When done right it looks very nice. Currently it is very complicated to make such a design that is purely theme based in Flutter.

    You can of course forget about a theme for your white app bar and just make a custom app bar widget that checks if its color is light and then sets its icon theme and text color to black or black87, then you can keep your Typography localization functionality. But imo, theming an app should really be made using themes and not via widgets in the app, to be easier to maintain and change from a central point.

    Clearly a better way to make light or white AppBarTheme is needed.

    It would be simple if the AppBar brightness property just forced appropriate black or white typography for the text and icons in the AppBar, unless you have explicitly specified something else. Other users seem to assume it should do just that as well: (#61618), so did I when I first tried it.

  5. timsneath commented on Aug 2, 2020

    @timsneath

    Would you consider submitting a pull request to fix this, @rydmike? I suspect you've followed this rabbit hole far enough to have a good idea of the fix... Also /cc @HansMuller for interest.

  6. feinstein commented on Aug 2, 2020

    @feinstein
    ContributorAuthor

    IIRC @HansMuller was changing several themes, starting for the new Buttons theme that's about to be released... I memory doesn't fails the AppBar's theme system was in the same roadmap, so this issue might be a part of a bigger future change in the framework entirely.

  7. rydmike commented on Aug 3, 2020

    @rydmike
    Contributor

    @feinstein I agree it is indeed quite an involved topic and I am aware of the redesign/improvements going on with the entire Theme design. I think I read a design doc on it a while ago, but can't find it of the top of my head. I have also noticed that @HansMuller has been doing a lot of work on themes lately. I hope he has a chance to look at the challenges with the AppBar theme too. It might actually be a bit tricky solve due to legacy reasons without breaking things, well maybe easier if you add some new features, probably the new targeted way of handling all sub-themes can handle the needed scenario if done so in the app bar theme.

    @timsneath My exploration was more of a discovery and learning what is is going on with this challenge.
    I dug into after noticing how difficult it was to create a white app bar theme for an overall "light" theme where the primary color is dark, which it mostly is for a light theme. I asked around in a few communities and everybody that tried it ended up either with a white app bar text theme (invisible on the white app bar) or an app bar text theme that was black, but without geometry (small default font). This when the constraint was that you have to do it with just the Theme factory, create a ThemeData class (and its context free helpers) without any context that you can then later apply to your app or anywhere.

    Considering how popular the white app bar look is on iOS and that it gets copied a lot also on Android now, I think it does need to be easier to do this correctly with a theme.

    Also I still have no idea how I should do it (given the above constraints) in away that would allow me to keep any typography localisations applied to the app bar text theme later. The above workaround example ignores that for now and actually breaks the localization since it no longer uses any of the 3 standard built in text theme's in ThemeData (textTheme, primaryTextTheme, accentTextTheme), but it works for my use case since I don't yet need those localisations, the English like is fine.

    Generally I think it can sometimes be a bit problematic that we cannot get a ThemeData object with geometry before we have a context that can be used to localize it. But I understand why it is like this. I guess as long as we can create ThemeData object, including all of its sub theme by just using textTheme() when sub themes need a text theme and it then also gets correct black/white text theme for the surrounding theme conditions, that then get localized correctly, it can go pretty far as is. I think this actually worked for some sub themes I tried it on, but for some you get a null error if you try it.

    On a concept level many of the issues with the default color of the text theme is imo rooted in it all being based on textTheme, primaryTextTheme and accentTextTheme in ThemeData, instead of just having one dark and light text, that would get used when the surface that the text is on, needs dark or light text for optimal contrast, but like I hinted that is a much bigger topic and this issue can certainly be fixed somehow within the current constraints.


    @HansMuller A bit of topic, but on the topic of theme issues and their development. Regarding the new way of theming, starting from a ColorScheme and using ThemeData.from(colorScheme). Is there a plan to include onColors for primaryVariant and secondaryVariant in a future revised version of ColorScheme?

    If you make a theme where the variant color actually needs another onColor than its corresponding primary (the example dark theme in the Material design guide is actually an example of this), there is no way of getting that info from the ColorScheme. You now have to check brightness separately when you want to use the variant colors, e.g. ThemeData.estimateBrightnessForColor(Theme.of(context).colorScheme.primaryVariant) just to know what text/icon brightness you should put on it, seems a bit tedious.

    If this is something that to your knowledge is not yet on the road-map, let me know and I can post an enhancement issue for it.

    I have also noticed that if you currently create a theme using ThemeData.from(colorScheme) that the theme for some sub-themes are not in-line with the supplied colorScheme, some widgets still get their defaults from the Theme factory defaults, which will not match the supplied colorScheme. I'll make a separate issue of which ones I have noticed that still have minor issues, perhaps they are also already being addressed. It was maybe only 2 or 3 cases that I have noticed recently. I will follow-up on this in another issue later.

  8. HansMuller commented on Dec 1, 2020

    @HansMuller
    Contributor

    @feinstein - if you have time to look, I hope you'll provide a little feedback about #71184 which updates AppBar with the overall goal of making color and text style customizations easier.

    In terms of your example, the text/icons foreground color can be specified explicitly with the changes from #71184:

    AppBar(
      backwardsCompatibility: false, // temporary
      foregroundColor: Colors.white,
      ...
    )

    The overall theme's AppBarTheme can be used to configure the foreground and background colors for all AppBars via its foregroundColor and backgroundColor properties.

  9. feinstein commented on Dec 2, 2020

    @feinstein
    ContributorAuthor

    @HansMuller I had a very long day but couldn't miss this update. I really enjoyed it! Specially the systemOverlayStyle, thank you, I was really missing this, as we discussed before.

  10. lig commented on Dec 2, 2020

    @lig

    @HansMuller just reading colorScheme.onPrimary if the theme's brightness is light for foregroundColor brings a lot of joy.

  11. rydmike commented on Dec 2, 2020

    @rydmike
    Contributor

    This is a really nice and welcome improvement. I added a comment here #71184 (comment)

    Basically just saying it look really good and that when this lands in master I will test it by trying the things I tried above in comment #50606 (comment). Just to see and demonstrate how it works in practice with the new features. When I have tried it I will add my findings as a comment to this thread.

  12. rydmike commented on Dec 4, 2020

    @rydmike
    Contributor

    Thanks again @HansMuller, I just tried it, and it works beautifully, just like you mentioned here too #71184 (comment) and of course also when using just the old default ThemeData() factory as well, instead of the simpler and newer ThemeData.from ColorScheme based factories that you showed.

    Here just as a confirmation demo of this as well, since I mentioned above that I was going to show it for reference as an additional comment heer.

    An app with ThemeData like this using the new AppBarTheme() features, just applied to the default counter app:

    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'AppBar Theme Test',
          theme: ThemeData(
            brightness: Brightness.light,
            primarySwatch: Colors.blue,
            appBarTheme: AppBarTheme(
              backgroundColor: Colors.white,
              foregroundColor: Colors.black87,
              elevation: 0,
            ),
          ),
          home: MyHomePage(title: 'AppBar Theme Test'),
          debugShowCheckedModeBanner: false,
        );
      }
    }
    

    Easily now gives us a white app bar with dark text, even though the primary color is of brightness dark and in older version would have forced the text theme to white for the app bar. Nice! It also works if you switch the brighntess to Brightness..dark, the app bar keeps it colors as sepcified.

    image

    Practical usability issue during the migration period

    I think the only thing I would have wished for to make the migration easier and to get it to apply to all AppBars in an app, would be a way to toggle the desired default behavior via a flag in the overall ThemeData or in the AppBarTheme, kind of like you can with the useTextSelectionTheme.

    To have the temporary setting for the backwardsCompatibility flag either in the overall ThemeData or even just in the AppBarTheme, that then gives the default for the AppBar's own setting for it, that might as now have its own setting just in case there is a need to override the theme based setting on an AppBar level, perhaps for some migration use cases.

    As it is now, you have to go and set the backwardsCompatibility to false for every AppBar in your app where you want to use the new AppBar theming features. This actually makes opting in to use it difficult, or to migrate to it, since you have to go and set the backwardsCompatibility to false for every AppBar that you create/have in your app, and there might be quite a large number of them. Later when the flag is deprecated you have to remove it again from all instances.

    If it would be possible to set the flag also via a parameter in the AppBarTheme() in the example above, it would not be necessary to opt in again when you actually create the AppBars in your app, like below for this example. You can then omit it there since you already opted in via the AppBarTheme, which you probably just define in one place, or much fewer places than the AppBars at least.

    Maybe I missed something, but I tried looking in AppBarTheme and ThemeData, but I did not see any opt in option before the actual AppBar creation, it is as described above a bit impractical to only have the opt in option there. If that really is the case that is, maybe I missed something?

    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key, this.title}) : super(key: key);
    
      final String title;
    
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      int _counter = 0;
    
      void _incrementCounter() {
        setState(() {
          _counter++;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
            backwardsCompatibility: false,
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  'You have pushed the button this many times:',
                ),
                Text(
                  '$_counter',
                  style: Theme.of(context).textTheme.headline4,
                ),
              ],
            ),
          ),
          drawer: Drawer(),
          endDrawer: Drawer(),
          floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ),
        );
      }
    }
  13. flutterq commented on Jan 15, 2021

    @flutterq
  14. github-actions commented on Aug 7, 2021

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  15. locked as resolved and limited conversation to collaborators on Aug 7, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions