Toggle theme example
A simple toggle-theme feature built using flutter_solidart
See the code here.
This simple example uses a powerful feature of flutter_solidart
, the Solid
widget.
The Solid
widget is used to provide signals to descendants without passing them as parameters.
First of all let's use the default light
and dark
themes by adding them to our MaterialApp
:
MaterialApp(
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
)
themeMode
is an Enum
used by the MaterialApp
to choose which theme to use: [light, dark, system]
.
To update the theme mode we've to rebuild our MaterialApp
.
Let's wrap the MaterialApp with a Solid
widget:
Solid(
child: MaterialApp(
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
),
)
Solid
takes a providers
list, let's create our themeMode
signal:
Solid(
providers: [
Provider<Signal<ThemeMode>>(
create: () => Signal(ThemeMode.light),
),
],
child: MaterialApp(
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
),
)
Provider
otherwise you may encounter unexpected errorsNow we've provided the themeMode
signal to descendants, but we've to observe the themeMode
signal in order to rebuilt the MaterialApp
.
Since we need a BuildContext
that is a descendant of Solid
we wrap the MaterialApp
inside a Builder
.
Builder(
builder: (context) {
return MaterialApp(
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
);
}
)
Now using the context.observe
method we effectively listen to the themeMode
signal:
Builder(
builder: (context) {
final themeMode = context.observe<Signal<ThemeMode>>().value;
return MaterialApp(
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
themeMode: themeMode,
);
}
)
Provide the exact type of the signal value also to the observe()
method.
This little line of code will subscribe the context
we've used to the signal
and will rebuild every time the themeMode
signal changes.
Finally pass the themeMode
value to the MaterialApp
.
In this example we've a page called MyHomePage
that displays a simple IconButton
that toggles the theme mode.
The page retrieves the themeMode
signal using the context.get()
method:
@override
Widget build(BuildContext context) {
final themeMode = context.get<Signal<ThemeMode>>();
...
}
Signal
or ReadSignal
if you need to access a read-only signalThe get
method obtains the signal for the given identifier (optional) without listening to it. You may use this method inside the initState()
, build()
methods and inside callbacks like onTap
or onPressed
.
To react to the signal we've used a SignalBuilder
:
SignalBuilder(
builder: (_, __) {
return IconButton(
icon: Icon(
themeMode.value == ThemeMode.light ? Icons.dark_mode : Icons.light_mode,
),
);
},
)
and to update our themeMode
signal we just update the signal value inside the onPressed
callback:
SignalBuilder(
builder: (_, __) {
final mode = themeMode.value;
return IconButton(
onPressed: () {
// toggle the theme mode
if (mode == ThemeMode.light) {
themeMode.value = ThemeMode.dark;
} else {
themeMode.value = ThemeMode.light;
}
},
icon: Icon(
mode == ThemeMode.light ? Icons.dark_mode : Icons.light_mode,
),
);
},
)
Testing
Writing a test that toggles the dark mode on and off.
testWidgets(
'Check that when the app is in light mode the icon button shows a moon, while in dark mode it shows a sun',
(WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
// Icon finders
Finder lightModeIcon() => find.byIcon(Icons.light_mode);
Finder darkModeIcon() => find.byIcon(Icons.dark_mode);
// Given that our theme starts at light mode
// Verify that the toggle theme icon button shows the dark mode icon
expect(darkModeIcon(), findsOneWidget);
expect(lightModeIcon(), findsNothing);
// Tap the icon button to toggle the theme mode and trigger a frame.
await tester.tap(darkModeIcon());
await tester.pump();
// Verify that our theme has changed to 'dark' mode and the `light_mode` icon should be shown
expect(lightModeIcon(), findsOneWidget);
expect(darkModeIcon(), findsNothing);
// Tap the icon button to toggle the theme mode and trigger a frame.
await tester.tap(lightModeIcon());
await tester.pump();
// Verify that our theme has changed to 'light' mode and the `dark_mode` icon should be shown
expect(darkModeIcon(), findsOneWidget);
expect(lightModeIcon(), findsNothing);
});