Dark Mode
How to write a Dark Mode Flutter application with UtopiaHooks.
To visit Dark Mode repository press HERE.
Project Structureβ
|- dark_mode.dart
|- theme
| |- app_colors.dart
| |- app_texts.dart
|- global_state
| |- theme_state.dart
|- ui
| |- dark_mode_page.dart - Coordinator between state & view layers
| |- state
| | |- dark_mode_page_state.dart - Layer that definies State and Hook responsible for business-logic
| |- view
| | |- dark_mode_page_view.dart
|- util
| |- context_extension.dart
For more info about our recommended directory structure visit our Guide.
DarkModeAppβ
In able to initialize and read hook providers whole application needs to be wrapped with HookProviderContainerWidget
.
Declared Global States
can be accessed GlobalStates can be accessed either through BuildContext
and get()
function
or useProvided<T>()
hook.
Learn more about Global States.
class DarkModeApp extends StatelessWidget {
const DarkModeApp();
Widget build(BuildContext context) {
return const HookProviderContainerWidget(
{ThemeState: useThemeState},
child: MaterialApp(
title: 'Flutter Demo',
home: DarkModePage(),
),
);
}
}
ThemeState πβ
theme_state.dart
is responsible for providing and managing theme. It consists of two
segments: ThemeState
object and useThemeState
Hook responsible for state management.
useThemeState
implements useState hook determining current theme mode and
two useMemoized hooks.
They are responsible for caching and updating themes currently used by the application.
import 'package:utopia_hooks/utopia_hooks.dart';
import 'package:utopia_hooks_example/dark_mode/theme/app_colors.dart';
import 'package:utopia_hooks_example/dark_mode/theme/app_text.dart';
class ThemeState {
final bool darkMode;
final AppColors colors;
final AppText texts;
final void Function() onModeChanged;
const ThemeState({
required this.colors,
required this.darkMode,
required this.text,
required this.changeType,
});
}
ThemeState useThemeState() {
final darkModeState = useState<bool>(false);
final colors = useMemoized(() => darkModeState.value ? AppColors.light : AppColors.dark, [darkModeState.value]);
final text = useMemoized(() => AppTexts(colors: colors), [colors]);
return ThemeState(
darkMode: darkModeState.value,
onModeChanged: darkModeState.toggle,
colors: colors,
texts: texts,
);
}
BuildContextExtension πβ
Extension for easier access to theme
extension BuildContextExtension on BuildContext {
ThemeState get theme => get<ThemeState>();
AppColors get colors => theme.colors;
AppText get texts => theme.texts;
}
DarkModePageState πβ
DarkModePageState
handles local business-logic of DarkModePage
. In this case it serves as a bridge between View
layer and the ThemeState
, which is accessed via the useProvided
hook.
class DarkModePageState {
final bool darkMode;
final void Function() onThemeChanged;
const DarkModePageState({
required this.onThemeChanged,
required this.darkMode,
});
}
DarkModePageState useDarkModePageState() {
final themeState = useProvided<ThemeState>();
return DarkModePageState(
onThemeChanged: themeState.onModeChanged,
darkMode: themeState.darkMode,
);
}
DarkModePage - Coordinator πβ
We start with a simple wrapper that serves as a bridge between our state and view. In this simple case it's only
responsible for initializing DarkModePageState
.
In more complex structures, it also provides Flutter-related functions, such
as Navigator.of(context).push
to useDarkModePageState
Hook.
class DarkModePage extends StatelessWidget {
const DarkModePage();
Widget build(BuildContext context) {
return const HookCoordinator(
use: useDarkModePageState,
builder: DarkModePageView.new,
);
}
}
DarkModePageView πβ
FormValidationPageView is responsible for displaying UI and Button changing current theme. It uses
the DarkModePageState
passed by DarkModePage
.
Keep in mind that the View gets current Theme via our BuildContextExtension
.
class DarkModePageView extends StatelessWidget {
final DarkModePageState state;
const DarkModePageView(this.state);
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: context.colors.canvas,
appBar: AppBar(
backgroundColor: context.colors.field,
title: Text("Flutter demo dark mode", style: context.texts.body),
),
floatingActionButton: FloatingActionButton(
onPressed: state.onThemeChanged,
backgroundColor: context.colors.field,
tooltip: 'Change mode',
child: Icon(
state.darkMode ? Icons.nightlight_outlined : Icons.sunny,
color: context.colors.icon,
),
),
);
}
}
AppColors & AppTextβ
AppColors
and AppText
are simple classes containing parts of the application styles.
AppColorsβ
class AppColors {
final Color text;
final Color canvas;
final Color field;
final Color icon;
const AppColors._({
required this.text,
required this.canvas,
required this.icon,
required this.field,
});
static AppColors light = AppColors._(
text: const Color(0xFF060542),
canvas: const Color(0xFFE2EAF6),
field: Colors.grey[200]!,
icon: const Color(0xFF04266C),
);
static AppColors dark = AppColors._(
text: const Color(0xFFFFFFFF),
canvas: const Color(0xFF00030E),
field: Colors.grey[900]!,
icon: Colors.yellow,
);
}
AppTextsβ
class AppTexts {
final AppColors colors;
const AppTexts({required this.colors});
TextStyle get _base => TextStyle(fontFamily: "Roboto", color: colors.text);
TextStyle get body => _base.copyWith(fontWeight: FontWeight.w500, fontSize: 12);
}
Crafted specially for you by UtopiaSoftware π½