Adaptive Theme: light and dark modes that survive a restart in Flutter
Easiest way to add support for light and dark theme in your flutter app.
At a glance
- What is it?
- Adaptive Theme is a Flutter package that wires a light and a dark ThemeData into your MaterialApp, lets the user pick light, dark or follow the system, and persists the choice across app restarts. It also covers the startup flicker problem, a floating toggle button for testing, and Cupertino themes.
- Who is it for?
- Adopt Adaptive Theme if your Flutter app already has two ThemeData objects and you want a theme mode switch that persists, plus the machinery to avoid a flash of the wrong theme on startup. Do not adopt it if you need more than one theme beyond a light and a dark variant, since the model is built around that pair.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 70 days ago.
- What is it written in?
- Mainly Dart, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the package actually solves
The README describes the package as the easiest way to add support for light and dark themes in a Flutter app. It allows you to manually set a light or dark theme and also lets you define themes based on the system, and it persists the theme mode changes across app restarts. Those three capabilities are the whole pitch, and they are worth separating because they carry different costs. The manual set and the system-following behaviour are small amounts of code. The persistence is the part that usually ends up being hand rolled badly, because it means reading a stored value before the first frame and dealing with what happens when the stored value arrives too late. The package is published on pub.dev as adaptive_theme, licensed Apache 2.0, written in Dart, with the primary language of the repository listed as Dart, and the recent releases are 3.7.2 from 2025-10-07, 3.7.1+2 and 3.7.1+1 both from 2025-08-18, and the last push was on 2026-08-14.
Wrapping MaterialApp with AdaptiveTheme
Installation is the usual pubspec dependency, and the README asks you to add the latest version.
dependencies:
adaptive_theme: <latest_version>The key structural change is that you wrap your MaterialApp in an AdaptiveTheme widget, and you pass a builder that constructs the MaterialApp. The builder receives the light and dark themes that AdaptiveTheme has resolved, which is how the package keeps a single source of truth for the current mode.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return AdaptiveTheme(
light: ThemeData.light(useMaterial3: true),
dark: ThemeData.dark(useMaterial3: true),
initial: AdaptiveThemeMode.light,
builder: (theme, darkTheme) => MaterialApp(
title: 'Adaptive Theme Demo',
theme: theme,
darkTheme: darkTheme,
home: MyHomePage(),
),
);
}
}Note that the initial mode is an explicit AdaptiveThemeMode.light here rather than a system follow, and that both themes are built with useMaterial3 enabled. The package is a wrapper rather than a replacement, so your MaterialApp keeps its own theme and darkTheme parameters and AdaptiveTheme just decides which one is live.
The mode API: set, toggle, reset and setTheme
The runtime API is reached through AdaptiveTheme.of(context), and the surface is small. There are three setters for the mode itself, setDark, setLight and setSystem, and one toggle that moves between light, dark and system. Then there is setTheme, which replaces the theme pair entirely rather than just flipping the mode, which is how you would change the whole colour scheme.
// sets theme mode to dark
AdaptiveTheme.of(context).setDark();
// sets theme mode to light
AdaptiveTheme.of(context).setLight();
// sets theme mode to system default
AdaptiveTheme.of(context).setSystem();AdaptiveTheme.of(context).toggleThemeMode();AdaptiveTheme.of(context).setTheme(
light: ThemeData(
useMaterial3: true,
brightness: Brightness.light,
colorSchemeSeed: Colors.purple,
),
dark: ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
colorSchemeSeed: Colors.purple,
),
);The reset method is what keeps the package forgiving. AdaptiveTheme holds on to the default themes you supplied at initialization, so AdaptiveTheme.of(context).reset() returns to those and resets the mode as well, which means a user who has wandered into a strange colour scheme can be returned to a known state without restarting the app.
The startup flicker, and the async main() that avoids it
The most useful section of the README is the one on getting the theme mode at app start, because it describes a problem you will otherwise discover in production. When you change the theme, the next app run cannot pick up the most recent theme directly before rendering with the default theme for the first time, since at initialization the package cannot run async code to get the previous theme mode. The workaround is to make main() async and load the stored mode before the first frame.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final savedThemeMode = await AdaptiveTheme.getThemeMode();
runApp(MyApp(savedThemeMode: savedThemeMode));
}You then feed the retrieved mode into the initial parameter of AdaptiveTheme, falling back to light when nothing has been stored, which the README says helps to avoid theme change flickering on app startup. This is the part of the package that saves you real work, because the alternative is a theme that flashes the wrong brightness for a frame on every cold start, and users read that as a bug in your app rather than in the theme system.
Reacting to mode changes and the floating test button
If you are building a settings screen you will want to observe the mode, and the package exposes a ValueNotifier for it. You can add a listener to it, or drive UI from it with a ValueListenableBuilder, which is the pattern the README shows for updating a widget when the mode changes. There is also a convenience for developers that arrived in v3.3.0: setting debugShowFloatingThemeButton to true enables a floating button that toggles the theme mode, and the README describes it as useful when you want to test the app in both light and dark themes without restarting the app or navigating to the settings screen where the theme settings live. It is a debug affordance, not something to ship in a release build, and the fact that it sits behind a debug prefixed flag is the package signalling that intent.
Caveats, SharedPreferences and Cupertino
The README has a caveats section and it is worth reading before you promise anything to a product owner. There is a subsection on non-persistent theme changes, which covers what happens when you change the theme through setTheme rather than through the mode setters, and a subsection on using SharedPreferences, which is the storage the package uses to remember the mode. Understanding which parts of the package write to storage and which do not is what lets you predict whether a user will see their choice survive a restart. The package also covers Cupertino apps, with a section on changing the Cupertino theme, so an app that is not Material based is not excluded. The final piece is the ability to set a different default theme than the one provided at initialization, done by calling setTheme with an isDefault flag set to true. The README notes this is only useful when you might want to reset to the default theme at some point, which is the case when you fetch themes remotely on app start and want the fetched theme to be the current one but the original to remain the fallback.
Where the package stops, and the alternative to weigh
The model is a light theme and a dark theme, with a mode that selects between them or defers to the system. If your product needs more than that, for example several named colour schemes a user picks from a gallery, or a theme that follows the system wallpaper, this package is the wrong shape and you are back to holding the ThemeData yourself. The second limit is the dependency on persistent storage working, since the package relies on SharedPreferences, and a user clearing app data, or a platform where preferences are unavailable, changes what persists. The alternative worth comparing is the plain Flutter approach, where you keep a ThemeMode in your own state, wrap the app in a ValueNotifier or a small inherited widget, and store the mode in SharedPreferences yourself. The package earns its place mainly through the startup handling and the mode notifier, not through the theme switching, which is a handful of lines either way. The repository layout also hints at the shape of the project: a pubspec.yaml, an analysis_options.yaml for the Dart analyzer, a packages/ directory, a .run/ directory for the run configurations, a CODE_OF_CONDUCT.md and a PULL_REQUEST_TEMPLATE.md, and a separate .copyright file.
Editorial conclusion
Adopt Adaptive Theme if your Flutter app already has two ThemeData objects and you want a theme mode switch that persists, plus the machinery to avoid a flash of the wrong theme on startup. Do not adopt it if you need more than one theme beyond a light and a dark variant, since the model is built around that pair. Before you wire it in, read the caveats section on non-persistent changes and on SharedPreferences, because both change what the package promises, and check the minimum Flutter version against your project since the package uses Material 3 features in its examples.
Frequently asked questions
What does Adaptive Theme do for a Flutter app?
It lets you define a light and a dark theme, switch manually between light, dark and a system-following mode, and persists the theme mode changes across app restarts. It is published on pub.dev as adaptive_theme under the Apache 2.0 licence.
How do I set up Adaptive Theme in a Flutter project?
Add adaptive_theme to your pubspec.yaml dependencies, then wrap your MaterialApp in an AdaptiveTheme widget, passing light, dark, an initial mode and a builder that constructs the MaterialApp with the theme and darkTheme it hands you.
How do I avoid a theme flicker when the app starts?
The README says the previous mode cannot be read before the first frame, so you make main() async, call WidgetsFlutterBinding.ensureInitialized(), await AdaptiveTheme.getThemeMode() and pass the result as the initial mode. This avoids the theme change flickering on app startup.
How do I change the theme colours rather than the mode?
You call setTheme with a new light and dark ThemeData pair, which replaces the theme pair rather than flipping the mode. To make the new pair the default that reset() returns to, pass isDefault set to true.
What is the floating theme button in Adaptive Theme?
Since v3.3.0 you can set debugShowFloatingThemeButton to true to enable a floating button that toggles the theme mode. The README describes it as useful for testing an app in both light and dark themes without restarting the app or navigating to the settings screen.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/birjuvachhani-adaptive-theme)Was this useful?