Easiest way to add support for light and dark themes in your Flutter app. It allows you to manually set light or dark themes and also lets you define themes based on the system. It also persists that the theme modes change across app restarts.
Demo: Adaptive Theme
Index
- Getting Started
- Initialization
- Changing Theme Mode
- Toggle Theme Mode
- Changing Themes
- Reset Theme
- Set Default Theme
- Handling App Start
- Handling Theme Changes
- Using a floating theme button overlay
- Caveats
- Non-Persist theme changes
- Using SharedPreferences
- Using-CupertinoTheme
- Changing Cupertino Theme
- Contribution
Getting Started
add following dependency to your pubspec.yaml
dependencies:
adaptive_theme: <latest_version>
Initialization
You need to wrap your MaterialApp
with AdaptiveTheme
in order to apply themes.
import 'package:adaptive_theme/adaptive_theme.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return AdaptiveTheme(
light: ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.red,
accentColor: Colors.amber,
),
dark: ThemeData(
brightness: Brightness.dark,
primarySwatch: Colors.red,
accentColor: Colors.amber,
),
initial: AdaptiveThemeMode.light,
builder: (theme, darkTheme) => MaterialApp(
title: 'Adaptive Theme Demo',
theme: theme,
darkTheme: darkTheme,
home: MyHomePage(),
),
);
}
}
Changing Theme Mode
Now that you have initialized your app as mentioned above. It’s very easy and straightforward to change your theme modes: light to dark, dark to light or to system default.
// 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();
Toggle Theme Mode
AdaptiveTheme
allows you to toggle between light, dark, and system themes in the easiest way possible.
AdaptiveTheme.of(context).toggleThemeMode();
Changing Themes
If you want to change the theme entirely like changing all the colors to some other color swatch, then you can use setTheme
method.
AdaptiveTheme.of(context).setTheme(
light: ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.purple,
accentColor: Colors.amber,
),
dark: ThemeData(
brightness: Brightness.dark,
primarySwatch: Colors.purple,
accentColor: Colors.amber,
),
);
Reset Theme
AdaptiveTheme
is smart enough to keep the default themes handy that you provided at the time of initialization. You can fall back to those default themes in a very easy way.
AdaptiveTheme.of(context).reset();
This will reset your theme as well as theme mode to the initial values provided at the time of initialization.
Set Default Theme
AdaptiveTheme
persists theme mode changes across app restarts and uses the default themes to set theme modes(light/dark) on. You can change this behavior if you want to set a different theme as the default theme other than the one provided at the time of initialization.
This comes handy when you’re fetching themes remotely on app starts and setting the theme as current theme.
Doing so is quit easy. You would set a new theme normally as you do by calling setTheme
method but this time, with a flag isDefault
set to true.
This is only useful when you might want to reset to default theme at some point.
AdaptiveTheme.of(context).setTheme(
light: ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.blue,
accentColor: Colors.amber,
),
dark: ThemeData(
brightness: Brightness.dark,
primarySwatch: Colors.blue,
accentColor: Colors.amber,
),
isDefault: true,
);
Get ThemeMode at App Start
When you change your theme, next app run won’t be able to pick the most recent theme directly before rendering with default theme first time. This is because at time of initialization, we cannot run async code to get previous theme mode. However it can be avoided if you make your main()
method async and load previous theme mode asynchronously. Below example shows how it can be done.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final savedThemeMode = await AdaptiveTheme.getThemeMode();
runApp(MyApp(savedThemeMode: savedThemeMode));
}
AdaptiveTheme(
light: lightTheme,
dark: darkTheme,
initial: savedThemeMode ?? AdaptiveThemeMode.light,
builder: (theme, darkTheme) => MaterialApp(
title: 'Adaptive Theme Demo',
theme: theme,
darkTheme: darkTheme,
home: MyHomePage(),
),
)
Notice that I passed the retrieved theme mode to my material app so that I can use it while initializing the default theme. This helps avoiding theme change flickering on app startup.
Listen to the theme mode changes
You can listen to the changes in the theme mode via a ValueNotifier
. This can be useful when designing theme settings screen or developing ui to show theme status.
AdaptiveTheme.of(context).modeChangeNotifier.addListener(() {
// do your thing.
});
Or you can utilize it to react on UI with
ValueListenableBuilder(
valueListenable: AdaptiveTheme.of(context).modeChangeNotifier,
builder: (_, mode, child) {
// update your UI
return Container();
},
);
Using floating theme button overlay
Starting from v3.3.0
, you can now set debugShowFloatingThemeButton
to true
and enable a floating button that can be used to toggle theme mode very easily. This is useful when you want to test your app with both light and dark theme without restarting the app or navigating to settings screen where your theme settings are available.
AdaptiveTheme(
light: ThemeData.light(),
dark: ThemeData.dark(),
debugShowFloatingThemeButton: true, // <------ add this line
initial: AdaptiveThemeMode.light,
builder: (theme, darkTheme) => MaterialApp(
theme: theme,
darkTheme: darkTheme,
home: MyHomePage(),
),
);
floating_button.mp4
Caveats
Non-Persist theme changes
This is only useful in scenarios where you load your themes dynamically from network in the splash screen or some initial screens of the app. Please note that
AdaptiveTheme
does not persist the themes, it only persists the theme modes(light/dark/system). Any changes made to the provided themes won’t be persisted and you will have to do the same changes at the time of the initialization if you want them to apply every time app is opened. e.g changing the accent color.
Using SharedPreferences
This package uses shared_preferences plugin internally to persist theme mode changes. If your app uses shared_preferences which might be the case all the time, clearing your shared_preferences at the time of logging out or signing out might clear these preferences too. Be careful not to clear these preferences if you want it to be persisted.
/// Do not remove this key from preferences
AdaptiveTheme.prefKey
You can use above key to exclude it while clearing the all the preferences.
Or you can call AdaptiveTheme.persist()
method after clearing the preferences to make it persistable again as shown below.
final prefs = await SharedPreferences.getInstance();
await pref.clear();
AdaptiveTheme.persist();
Using CupertinoTheme
Wrap your CupertinoApp
with CupertinoAdaptiveTheme
in order to apply themes.
import 'package:adaptive_theme/adaptive_theme.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CupertinoAdaptiveTheme(
light: CupertinoThemeData(
brightness: Brightness.light,
),
dark: CupertinoThemeData(
brightness: Brightness.dark,
),
initial: AdaptiveThemeMode.light,
builder: (theme) => CupertinoApp(
title: 'Adaptive Theme Demo',
theme: theme,
darkTheme: darkTheme,
home: MyHomePage(),
),
);
}
}
Changing Cupertino Theme
// sets dark theme
CupertinoAdaptiveTheme.of(context).setDark();
// sets light theme
CupertinoAdaptiveTheme.of(context).setLight();
// sets system default theme
CupertinoAdaptiveTheme.of(context).setSystem();
Contribution
You are most welcome to contribute to this project!
Please have a look at Contributing Guidelines, before contributing and proposing a change.
Download source code on GitHub
https://github.com/birjuvachhani/adaptive_theme
Provides the list of the opensource Flutter apps collection with GitHub repository.