Is it possible "extend" ThemeData in Flutter
I recommend this approach, which is simple, works with hot reload and can be easily extended to support switching between dark and light themes.
First create your own analog to ThemeData
, let's call it AppThemeData
:
class AppThemeData {
final BorderRadius borderRadius = BorderRadius.circular(8);
final Color colorYellow = Color(0xffffff00);
final Color colorPrimary = Color(0xffabcdef);
ThemeData get materialTheme {
return ThemeData(
primaryColor: colorPrimary
);
}
}
The materialTheme
can be used whenever the standard ThemeData
is needed.
Then create a widget called AppTheme
, which provides an instance of AppThemeData
using the provider
package.
class AppTheme extends StatelessWidget {
final Widget child;
AppTheme({this.child});
@override
Widget build(BuildContext context) {
final themeData = AppThemeData(context);
return Provider.value(value: themeData, child: child);
}
}
Finally, wrap the whole app with AppTheme
. To access the theme you can call context.watch<AppThemeData>()
. Or create this extension...
extension BuildContextExtension on BuildContext {
AppThemeData get appTheme {
return watch<AppThemeData>();
}
}
... and use context.appTheme
. I usually put final theme = context.appTheme;
on the first line of the widget build method.
Updated for null-safety
I've extended standard ThemeData
class so that at any time one could access own theme fields like that:
Theme.of(context).own().errorShade
Or like that:
ownTheme(context).errorShade
A theme can be defined and extended with new fields as follows(via addOwn()
called on a certain ThemeData
instance):
final ThemeData lightTheme = ThemeData.light().copyWith(
accentColor: Colors.grey.withAlpha(128),
backgroundColor: Color.fromARGB(255, 255, 255, 255),
textTheme: TextTheme(
caption: TextStyle(
fontSize: 17.0, fontFamily: 'Montserrat', color: Colors.black),
))
..addOwn(OwnThemeFields(
errorShade: Color.fromARGB(240, 255, 200, 200),
textBaloon: Color.fromARGB(240, 255, 200, 200)));
final ThemeData darkTheme = ThemeData.dark().copyWith( ...
...
Themes can be applied to MaterialApp
widget in a conventional way:
MaterialApp(
...
theme: lightTheme,
darkTheme: darkTheme,
)
The idea is to put all custom fields required for theming in a separate class OwnThemeFields
.
Then extend ThemeData
class with 2 methods:
-
addOwn()
that connects a certain instance ofThemedData
toOwnThemeFields
instance -
own()
that allows to lookup for own fields associated with the given theme data
Also ownTheme
helper method can be created to shorten the extraction of own fields.
class OwnThemeFields {
final Color? errorShade;
final Color? textBaloon;
const OwnThemeFields({Color? errorShade, Color? textBaloon})
: this.errorShade = errorShade,
this.textBaloon = textBaloon;
factory OwnThemeFields.empty() {
return OwnThemeFields(errorShade: Colors.black, textBaloon: Colors.black);
}
}
extension ThemeDataExtensions on ThemeData {
static Map<InputDecorationTheme, OwnThemeFields> _own = {};
void addOwn(OwnThemeFields own) {
_own[this.inputDecorationTheme] = own;
}
static OwnThemeFields? empty = null;
OwnThemeFields own() {
var o = _own[this.inputDecorationTheme];
if (o == null) {
if (empty == null) empty = OwnThemeFields.empty();
o = empty;
}
return o!;
}
}
OwnThemeFields ownTheme(BuildContext context) => Theme.of(context).own();
Complete source: https://github.com/maxim-saplin/dikt/blob/master/lib/ui/themes.dart
You can't extend ThemeData
because then material components won't find it anymore.
You can just create and provide MyThemeData
in addition to the ThemeData
included in Flutter the same way.
Create a widget CustomThemeWidget
that extends InheritedWidget
and provide your custom theme there.
When you want to get a value from the current theme use
myTheme = CustomThemeWidget.of(context).myTheme;
To change the current theme change the MyThemeData
in CustomThemeWidget.myTheme
Update
Like shown in https://github.com/flutter/flutter/pull/14793/files, it should be possible to extend ThemeData
and provide it as ThemeData
by overriding runtimeType
See also the comment in https://github.com/flutter/flutter/issues/16487#event-1573761656
Dart 2.7 later, extension support
you can add extension for system class
only add instance property is easy, but if you would get a dynamic color
you need think about it. for example, Use a constant to get the colors in light and dark modes
Determine if it is dark mode
two ways
MediaQuery.of(context).platformBrightnes == Brightness.dark;
Theme.of(context).brightness == Brightness.dark;
As you can see, you need the context, the context
Add Extension for BuildContext
Here is the code
extension MYContext on BuildContext {
Color dynamicColor({int light, int dark}) {
return (Theme.of(this).brightness == Brightness.light)
? Color(light)
: Color(dark);
}
Color dynamicColour({Color light, Color dark}) {
return (Theme.of(this).brightness == Brightness.light)
? light
: dark;
}
/// the white background
Color get bgWhite => dynamicColor(light: 0xFFFFFFFF, dark: 0xFF000000);
}
How to use
import 'package:flutter/material.dart';
import 'buildcontext_extension.dart';
class Test extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
color: context.bgWhite,
);
}
}
Also
This color may require multiple files, so you can create a public.dart file to manage it all
Like This
public.dart
library public;
// Export some common header files
// extensions
export 'buildcontext_extension.dart';
DarkMode images support
Put the light images in the same category as the dark ones
some code
static String getImgPath(String name, {
String folder = '',
String format = 'png',
bool isDark = false,
bool needDark = true
}) {
String finalImagePath;
if (needDark) {
final folderName = isDark ? '${folder}_dark' : folder;
finalImagePath = 'assets/images/$folderName/$name.$format';
} else {
finalImagePath = 'assets/images/$folder/$name.$format';
}
String isDarkPath = isDark ? "🌙 DarkMode" : "🌞 LightMode";
print('$isDarkPath imagePath 🖼 $finalImagePath');
return finalImagePath;
}