Add custom property to ThemeData in Flutter - flutter

I need to change color of a widget based on theme. I have separate ThemeData for light and dark theme. Now is it possible to add a custom property to ThemeData so that I can change the color of the widget based on theme and using that custom property?

Sadly, it appears that you simply can't - and the Flutter team doesn't seem to be interested in adding this, given their suggestion.
I think this is a major flaw, because we can't benefit from Theme.of(context) to automatically update all of our Widget that consume this ThemeData.
While some may say that you can use extensions to add new properties, you effectively won't know how to differ between multiple ThemeData (unless you can use some properties like Brightness, but I think this is just too hacky and not reliable to do so).
The alternative is to create another InheritedWidget (just like they said in the issue mentioned above) to handle your custom theme properties.
Edit: It seems that a new PR has introduced the possibility to extend the ThemeData, but it hasn't landed in main or even stable yet.

Flutter has recenty introduced ThemeExtensions (thanks #guilherme-matuella for the PR link!)
You can get a pretty good idea on how to use the functionality by following examples from the main Flutter repo:
return MaterialApp(
title: MyApp._title,
theme: ThemeData.light().copyWith(
extensions: <ThemeExtension<dynamic>>[
const MyColors(
brandColor: Color(0xFF1E88E5),
danger: Color(0xFFE53935),
),
],
),
darkTheme: ThemeData.dark().copyWith(
extensions: <ThemeExtension<dynamic>>[
const MyColors(
brandColor: Color(0xFF90CAF9),
danger: Color(0xFFEF9A9A),
),
],
),
themeMode: isLightTheme ? ThemeMode.light : ThemeMode.dark,
home: Home(
isLightTheme: isLightTheme,
toggleTheme: toggleTheme,
),
);
Which you can later retrieve in your widget like so:
final MyColors myColors = Theme.of(context).extension<MyColors>();

Instead of adding custom property, we can extend ThemeData by extension function. For example, if we need a custom property for color, we can add extension function on ColorScheme. Color dependencies are now moved to Themedata.
// checking brightness to support dynamic themeing
extension CustomColorSchemeX on ColorScheme {
Color get smallBoxColor1 =>
brightness == Brightness.light ? Colors.blue : Colors.grey[400];
}
And then access that property through Theme.of(context)...
Container(
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context)
.colorScheme
.smallBoxColor1),
),

Related

Flutter app bar background color from seed not working

I am developing one app in Flutter. In this, I am trying to use useMaterial3 in ThemeData. So I used below code to set color in colorScheme.
static ThemeData lightThemeData = ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.red,
),
appBarTheme: AppBarTheme(
centerTitle: true,
elevation: 2,
titleTextStyle: textTheme.titleLarge,
),
);
I have assigned above lightThemeData in Main.dart to theme property. AppBar not showing proper color when I run the app. Please check below screenshot.
It is not showing proper Red Color. Anyone know, why this is happening?
Flutter version: 3.0.1
Note: It is working on Flutter v2.10.5 but not on 3.0.1
You can use something like this for the full red experience:
static ThemeData lightThemeData = ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.red,
primary: Colors.red,
secondary: Colors.red,
),
appBarTheme: AppBarTheme(
color: Colors.red,
//other options
),
);
Alternatively you can use fromSwatch like this:
static ThemeData lightThemeData = ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.red),
appBarTheme: AppBarTheme(
color: Colors.red,
//other options
),
);
But make sure to test every part of your app before using it because the documentation says:
For historical reasons, instead of using a [colorSchemeSeed] or [colorScheme], you can provide either a [primaryColor] or [primarySwatch] to construct the [colorScheme], but the results will not be as complete as when using generation from a seed color.
I think fromSwatch is designed for Material Design 2.
Following contains an external tool:
For the absolute control over the colors there is a nice tool at https://m3.material.io/theme-builder#/custom
From the left bar choose your colors, and export the result using the top right button(there is a Flutter option). Downloaded file contains the full schemes. You can override the colors you don't like and feed into the theme directly.
The seed color is used directly to create the tonal palette for primary tones. The ColorScheme.primary is then assigned tone 40 for light mode and tone 80 for dark mode, from the same tonal palette. Neither of these colors will be an exact match for the "primary" seed color you used, it will be a color from a tonal palette that matches the seed color, since it is the primary one.
Branding challange
If you for branding purposes require it to exactly match the provided seed color, you can always override ColorScheme.primary you got with copyWith using the original seed color. And then use that ColorScheme as input to your apps ThemeData. A bit of an extra step, but it will work and look OK too, because for primaries the other colors will still match and be in sync with this color.

Flutter 3 Web App: Custom Colors or Theme

I am (very) new to Flutter, so please go easy on me. šŸ˜Š I'm using Flutter 3 and Dart 2.17.
I am making a web app and am trying to set up the color scheme for my app. In CSS, I'd normally do something like this to define a set of colors for light and dark mode:
:root {
--alpha: #FFF;
--bravo: #F5F5F6;
}
#media (prefers-color-scheme: dark) {
--alpha: #222630;
--bravo: #171920;
}
I've read about Flutter's ThemeData, and as I understand it, it's a big list of styles so that if I were making an Android app, I'd have some sensible defaults for various pieces of the mobile UI. But since I am making a web app that has a fully custom UI, it feels like a lot of extra stuff I don't want.
What is a good way to define a bunch of color names and have them automatically switch their values when I switch between light and dark mode? Should I still use ThemeData? Or is there a simpler way when my UI is custom?
I'm hoping for something like this (this probably isn't valid Dart; I'm still learning):
//My view
Container(color: MyColors.alpha)
...and the values are read somewhere like this:
//Some other class
class MyColors {
final alpha = darkMode ? const Color(0XFF222630) : const Color(0xFFFFFFFF);
final bravo = darkMode ? const Color(0xFF171920) : const Color(0xFFF5F5F6);
}
Has anyone done something like this?
I'd suggest you use an #observable ThemeMode themeMode variable (read about the mobx state management here https://pub.dev/packages/mobx) to change the state of your whole widget tree starting from MaterialApp when the user changes the app theme, for example
themeMode: themeMode != null
? _controller.themeMode!
: WidgetsBinding.instance.window.platformBrightness == Brightness.light
? ThemeMode.light
: ThemeMode.dark,
and still use ThemeData to set which colors you want your components to use, like this
darkTheme: ThemeData(
scaffoldBackgroundColor: MyColors.alphaDark,
backgroundColor: MyColors.alphaDark,
cardColor: MyColors.alphaDark,)
theme: ThemeData(
scaffoldBackgroundColor: MyColors.bravoLight,
backgroundColor: MyColors.bravoLight,
cardColor: MyColors.bravoLight,)
class MyColors {
final alphaDark = const Color(0XFF222630);
final bravoLight = const Color(0xFFF5F5F6);
}
Container(color: Theme.of(context).cardColor)
And you can always create a custom ThemeData so you can call your components whatever you want

"theme: ThemeData..." apparently ignored starting with Flutter 2.5.0

I'm working through the startup_namer tutorial example from flutter.dev. Here's the definition of MyApp:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Startup Name Generator',
theme: ThemeData(
// Add the 3 lines from here...
primaryColor: Colors.white,
), // ... to here.
home: RandomWords());
}
}
It used to be that when I ran the app, the title bar would be white. But now, it retains the default (blue) theme. Looks like either ThemeData or at least primaryColor are being ignored. Does anyone know how to fix this? Thank you.
Checkout this link and this quote from the Flutter-Team:
The ThemeData accentColor, accentColorBrightness, accentIconTheme and
accentTextTheme properties have been deprecated.
The Material Design spec no longer specifies or uses an ā€œaccentā€ color
for the Material components. The default values for component colors
are derived from the overall themeā€™s color scheme. The ColorSchemeā€™s
secondary color is now typically used instead of accentColor and the
onSecondary color is used when a contrasting color is needed.
Also checkout the migration guide.
Regarding your appBar problem do this within the ThemeData():
appBarTheme: AppBarTheme(backgroundColor: Colors.white),
This appears to be a regression: https://github.com/flutter/flutter/issues/89839.
Nowhere on the Flutter site does it say that primaryColor has been deprecated.

Changing the font family in Flutter when using ThemeData.dark() or ThemeData.light()

I'm trying to set the font of my MaterialApp. Since I'm using the dark theme, I'd like to just use copyWith and then change the fontFamily. However, copyWith doesn't have an option to change the fontFamily.
MaterialApp(
theme: ThemeData.dark().copyWith(
fontFamily: 'MyFontFamily',
),
The named parameter 'fontFamily' isn't defined.
How do I keep the dark theme but also change the font family? (Same problem with ThemeData.light().)
I found a solution and am posting below.
If you look at the source code for ThemeData.light() and ThemeData.dark(), you can see that all it does is set the brightness value:
/// A default light blue theme.
///
/// This theme does not contain text geometry. Instead, it is expected that
/// this theme is localized using text geometry using [ThemeData.localize].
factory ThemeData.light() => ThemeData(brightness: Brightness.light);
/// A default dark theme with a teal secondary [ColorScheme] color.
///
/// This theme does not contain text geometry. Instead, it is expected that
/// this theme is localized using text geometry using [ThemeData.localize].
factory ThemeData.dark() => ThemeData(brightness: Brightness.dark);
That means to solve your problem you don't need to bother with ThemeData.light() or ThemeData.dark(). Just create a new ThemeData and set the brightness yourself in addition to the fontFamily:
MaterialApp(
theme: ThemeData(
brightness: Brightness.dark,
fontFamily: 'MyFontFamily',
),
Since, all you're doing is setting the brightness, I think the easier way would be to just use DefaultTextStyle.
DefaultTextStyle(
style: TextStyle(
fontFamily: 'YourFontFamily',
),
child: YourWidget(),
)

CupertinoThemeData doesn't use specified primary color?

I have a class where I specify ThemeData for my app.
I use this class to set the appropriate theme in either the MaterialApp or CupertinoApp.
return CupertinoApp(
//...
theme: AppicationTheme.iosTheme()
//...
);
My IOS theme is provided as follows
static CupertinoThemeData iosTheme(){
return CupertinoThemeData(primaryColor: myPrimaryColor);
}
However when trying to set the color on an Icon, the primary color is still default blue as if never set to my color.
You are probably using Theme.of(context).primaryColor,
switch Theme to CupertinoTheme.
example:
Icon(Icons.access_alarm,<br>
color: CupertinoTheme.of(context).primaryColor,
),