I am using Flutters built-in themes like this:
return MaterialApp(
theme: ThemeData.light().copyWith(
primaryColor: const Color(0xFF5E975A),
bottomAppBarColor: const Color(0xff282828),
// ... And so on
),
As you can see I am modifying the existing theme with copyWith. Now let's say I want a certain button to always have the Color(0xFFFF0000). Is there a way to add a new key to the existing theme?
Something like this:
ThemeData.light().copyWith(
...
).addKey(myCustomColor: const Color(0xFFFF0000))
If not, what is the best-practice way to define my custom color? I feel like just declaring a global static variable is not the intended way to implement this.
Update for Flutter 3:
Flutter has answered this exact question in the newest version of Flutter. You can now create ThemeExtensions.
This can look something like this (after defining your ThemeExtension):
MaterialApp(
theme: ThemeData.light().copyWith(
extensions: <ThemeExtension<dynamic>>[
CustomColors.light,
],
),
darkTheme: ThemeData.dark().copyWith(
extensions: <ThemeExtension<dynamic>>[
CustomColors.dark,
],
),
// other parameters...
);
In the future there will be definitely more information about this but for now have a look at this article. I took the sample from there.
I am still finding the best practice way of defining ThemeData. For now, I have already used 2 ways to achieve the custom colors:
1 Use Extension
// use it with "Theme.of(context).myCustomColor"
extension CustomThemeDataExt on ThemeData {
Color get myCustomColor {
if(brightness == Brightness.light){
return Color(0xFFFF0000);
} else {
...
}
}
...
}
...
2 Define a custom theme
// use it with "CustomTheme.of(context).myCustomColor"
class CustomTheme {
final BuildContext context;
const CustomTheme(this.context);
static CustomTheme of(BuildContext context) => CustomTheme(context);
Color get myCustomColor {
if(Theme.of(context).brightness == Brightness.light){
return Color(0xFFFF0000);
} else {
...
}
}
}
Both need to import the relative file.
Related
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
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.
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),
),
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,
),
To manage themes in my MaterialApp(), i use :
MaterialApp(
...
theme: isDarkTheme ? darkTheme() : lightTheme(),
)
darkTheme() and lightTheme() are just simple function that return ThemeData()
So, I use an inherited Widget to change the variable isDarkTheme and then i use the setState((){}) method and all the application is rebuild
But, I saw on the Flutter documentation that we can use darkTheme:.
I'm trying to do this :
MaterialApp(
...
theme: lightTheme(),
darkTheme: darkTheme()
)
And we can change the theme during the course of the application
darkTheme property is used to define a Theme to be used when the device enters Dark mode, So you can't force your app to use darkTheme property because it depends on the value of MediaQueryData.platformBrightness which is a read-only field.
You can rather define several Themes to be used as a value for Theme property, and switching between them during the app course by using a StreamBuilder wrapped around your MaterialApp,which won't cause the app to stop and rebuild like the case when you were using setState to change theme (you'll need rxdart package to apply the below solution):
//BehaviorSubject stream any changes immediately without explicit call
var themeSubject = BehaviorSubject<ThemeData>();
//This way the app won't rebuild when the user selects the same theme
Stream<ThemeData> getTheme() => themeSubject.stream.distinct();
void setTheme (MaterialColor color){
pointsSubject.sink.add(ThemeData(primarySwatch: color) ;
}
return StreamBuilder<ThemeData>(
stream: getTheme(),
initialData: ThemeData(
primarySwatch: Colors.blue,
primaryTextTheme: TextTheme(
title: TextStyle(color: Colors.white),
),
builder: (context, themeSnapshot){
return MaterialApp(theme: themeSnapshot.data);
}
):
Then use your InheritedWidget to access setTheme and change it as you please.