This package is a set of widgets and classes to help manages multiple themes or Dark/Light theme functionality in a Flutter app.
Add the stacked_themes package to the pubspec.yaml file
dependencies:
...
stacked_themes:
This package provides some basic classes to make theme management easier when building your flutter application. stacked_themes provides you with the basic functionality of swapping out the ThemeData provided to your app, which can be accessed using Theme.of(context)
. In addition to that it also provides you with an helper function where you can change your status bar color. Lets take a look at the code.
The package makes use of a ThemeManager
to manage all the theme functionalities. To start off we have to call the initialise function of the ThemeManager
before the app is started. Change your main function to return a Future
and, make it async and await the initialise static function call on ThemeManager
.
Future main() async {
await ThemeManager.initialise();
runApp(MyApp());
}
We start by wrapping our MaterialApp
or any other App
with our ThemeBuilder
. The builder function returns a context, regularTheme, darkTheme and a themeMode. We supply this to the MaterialApp
.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ThemeBuilder(
builder: (context, regularTheme, darkTheme, themeMode) => MaterialApp(
title: 'Flutter Demo',
theme: regularTheme,
darkTheme: darkTheme,
themeMode: themeMode,
...
),
);
}
}
This will rebuild your app and pass a new theme through the context for you to use within your application.
The ThemeBuilder
has a property called themes where you can supply a list of ThemeData
. This is usually only 2 or 3, light, dark, neutral but we’re keeping it a list so you can have as many as you’d like. To keep things clean we will return the list of themes from a function created in a different file. Create a new file called theme_setup.dart
. You can place it in the UI folders root location or leave it in root of lib. I haven’t decided where it’s the best yet so it's up to you
List<ThemeData> getThemes() {
return [
ThemeData(backgroundColor: Colors.blue, accentColor: Colors.yellow),
ThemeData(backgroundColor: Colors.white, accentColor: Colors.green),
ThemeData(backgroundColor: Colors.purple, accentColor: Colors.green),
ThemeData(backgroundColor: Colors.black, accentColor: Colors.red),
ThemeData(backgroundColor: Colors.red, accentColor: Colors.blue),
];
}
This is a pretty simple function but can start to look quite busy given how many of the theme properties you will use in your Themes. Which is why I recommend you keep it in a separate file. Go back to your main.dart file and supply the getThemes()
value to the themes
property of the ThemeBuilder
.
Widget build(BuildContext context) {
return ThemeBuilder(
themes: getThemes(),
builder: (context, regularTheme, darkTheme, themeMode) => MaterialApp(
title: 'Flutter Demo',
theme: regularTheme,
darkTheme: darkTheme,
themeMode: themeMode,
...
),
);
}
The way we swap between themes is by setting the index of the theme we want to use. It will start off with the first theme in the list. To update the theme to any other theme all you have to do in the UI is call getThemeManager(context)
to get the ThemeManager
and then call selectThemeAtIndex(1)
on it. This is the code to select the second theme in the list of themes supplied.
getThemeManager(context)
.selectThemeAtIndex(1);
You can get the currently selected theme index by using the selectedThemeIndex
property of ThemeManager
.
getThemeManager(context).selectedThemeIndex;
This will return the index of the currently enabled theme.
If you only want to use Light and Dark themes then you can supply that to the ThemeBuilder
instead of supplying multiple themes. When supplying Light and Dark themes you can also supply a defaultThemeMode, which is ThemeMode.system
by default. When you leave it as system
you're telling the ThemeManager
that you want the system to determine if you're using light or dark mode. This is how you would supply your dark and light theme.
Widget build(BuildContext context) {
return ThemeBuilder(
defaultThemeMode: ThemeMode.light,
darkTheme: ThemeData(
brightness: Brightness.dark,
backgroundColor: Colors.blue[700],
accentColor: Colors.yellow[700],
),
lightTheme: ThemeData(
brightness: Brightness.light,
backgroundColor: Colors.blue[300],
accentColor: Colors.yellow[300],
),
builder: (context, regularTheme, darkTheme, themeMode) => MaterialApp(
title: 'Flutter Demo',
theme: regularTheme,
darkTheme: darkTheme,
themeMode: themeMode,
...
),
);
}
To toggle between light and dark mode you can use the toggleDarkLightTheme
function on the ThemeManager
.
getThemeManager(context)
.toggleDarkLightTheme();
In addition to toggling the theme you can also use the setThemeMode
function to set the ThemeMode
on the ThemeManager
.
getThemeManager(context)
.setThemeMode(ThemeMode.light);
The ThemeManager
will remember the last set value and broadcast that the next time the ThemeManager
is constructed.
Sometimes you want to status bar color to change as well when you have a specific theme. Most apps leave this transparent to make use of the backgrounds color. If your app requires you to change the status bar color there’s functionality for that as well. The ThemeBuilder
has a property statusBarColorBuilder
which is a function that expects you to return a Color
. It provides you with the theme to be selected and you can use that to return the color you’d like to show for your status bar.
Widget build(BuildContext context) {
return ThemeBuilder(
statusBarColorBuilder: (theme) => theme.accentColor,
lightTheme: ThemeData(...),
darkTheme: ThemeData(...),
...
);
}
In the case above we return the accentColor of the theme for the statusbar color. This functionality will automatically check if the status text is visible and will change the text from either white to black depending on the backgrounds value intensity (how close it is to white / black).
If any of the themes are changed by the user during the lifetime of the app the theme that was last selected will automatically be applied when the app starts up again. This functionality is created through using the shared_preferences package.
Now that you’ve setup all your theme functionality lets look at how to use it in your UI. The first thing to know is you can access your theme data the normal way.
var theme = Theme.of(context);
This means the rest of your Flutter code still stays the same.
As you know. One of the rules I stick too is that there should be no UI related code or classes making use of the UI directly in any of your business logic. For that reason there is also a ThemeService
which will allow you to call the same functions required to change between themes. This service should be used as any of the other stacked_services that you’ve used in the past. First we register it with the locator.
locator.registerSingleton(() => ThemeService.getInstance());
// or add it to your third_party_services_module if you’re using injectable
Then use it in the ViewModel
or other services by getting it from the locator
. And that’s it. That’s all the functionality required. If you’d like to talk more about this please checkout the slack and ask your questions there.