Flutter App Themes using HydratedBloc

Payam Asefi
4 min readOct 29, 2020

--

We can simply change flutter app theme with BloC pattern and Shared-preferences library, however we can also achieve this goal using a BloC library extension called HydratedBloc. The hydrated_bloc package is an extension of the flutter_bloc library which automatically stores states so that they can be restored even if the app is closed and opened again later. In this tutorial we are going to create a simple app and change its theme using this library.

Default app layout

Nothing special here, just a simple app with one page:

main.dart

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Theme Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: const ChangeThemePage()
);
}
}

change_theme.dart

class ChangeThemePage extends StatefulWidget {
const ChangeThemePage();
@override
_ChangeThemePageState createState() => _ChangeThemePageState();
}

class _ChangeThemePageState extends State<ChangeThemePage> {
bool _isDarkMode=false;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Change Theme'),
),
body: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Dark Mode:'),
Switch(
value: _isDarkMode,
onChanged: (value){
setState(() {
_isDarkMode=value;
});
},
)
],
),
),
);
}
}

As you can see there isn’t much of a code in here. The app looks like this:

initial app layout

Adding BloC libraries

First we are going to add 3 libraries, bloc, flutter_bloc and hydrated_bloc:

bloc: ^6.1.0
flutter_bloc: ^6.0.6
hydrated_bloc: ^6.0.3

In the next step we add Bloc Events, currently we have 2 events, one for enabling light theme and another for enabling dark theme:

theme_event.dart

abstract class ThemeEvent{
const ThemeEvent();
}
class LightThemeEvent extends ThemeEvent{
const LightThemeEvent();
}
class DarkThemeEvent extends ThemeEvent{
const DarkThemeEvent();
}

We can add Bloc States but since we are only working with ThemeData, we can use ThemeData as our state.

Creating ThemeBloc is not much different from creating a normal bloc, the only difference is we are extending HydratedBloc .

class ThemeBloc extends HydratedBloc<ThemeEvent, ThemeData> {
ThemeBloc() : super(ThemeData.light());

@override
Stream<ThemeData> mapEventToState(ThemeEvent event) async* {
if (event is LightThemeEvent) yield ThemeData.light();
if (event is DarkThemeEvent) yield ThemeData.dark();
}

@override
ThemeData fromJson(Map<String, dynamic> json) {
try {
if (json['light'] as bool) return ThemeData.light();
return ThemeData.dark();
} catch (_) {
return null;
}
}

@override
Map<String, bool> toJson(ThemeData state) {
try {
return {'light': state == ThemeData.light()};
} catch (_) {
return null;
}
}
}

In the code above:

  • fromJson method used to read data from a file, this override method happens automatically and If there is a state stored on the disk, the value passed to super() is ignored and the stored state is picked as initial.
  • toJson stores apps current state, this method happens automatically after a state change.

Now that our bloc is ready, we can change our UI. First we add bloc to our main.dart

void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initializing hydratedBloc, we can also change
// the default location of storage file.
HydratedBloc.storage = await HydratedStorage.build();
runApp(MyApp());
}

class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_)=>ThemeBloc(),
child: BlocBuilder<ThemeBloc,ThemeData>(
builder: (BuildContext context, theme)=>
MaterialApp(
title: 'Flutter Theme Demo',
theme: theme,
home: ChangeThemePage(theme==ThemeData.dark()),
)
),
);
}
}

And we also add bloc to change_theme.dart :

class ChangeThemePage extends StatefulWidget {
final bool isCurrentThemeDark;

const ChangeThemePage(this.isCurrentThemeDark);

@override
_ChangeThemePageState createState() => _ChangeThemePageState();
}

class _ChangeThemePageState extends State<ChangeThemePage> {
bool _isDarkMode;

@override
void initState() {
_isDarkMode = widget.isCurrentThemeDark;
super.initState();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Change Theme'),
),
body: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Dark Mode:'),
BlocListener<ThemeBloc, ThemeData>(
listener: (context, state) {
_isDarkMode = state == ThemeData.dark();
},
child: Switch(
value: _isDarkMode,
onChanged: (value) {
context.bloc<ThemeBloc>().add(value ? DarkThemeEvent() : LightThemeEvent());
setState(() {
_isDarkMode = value;
});
},
),
)
],
),
),
);
}
}

In this code:

  • We read isCurrentThemeDark from main widget to get the initial state of the Switch.
  • We also added BlocListener to the Switch to change its value.
  • With the help of context.bloc()<ThemeBloc> we fire new events using the switch.

Here is the result:

The code is also available from this github repository.

--

--

Payam Asefi
Payam Asefi

Written by Payam Asefi

Senior Flutter Developer with a passion for coding, movies, and nature. Let's connect and code together!

No responses yet