Skip to content

Instantly share code, notes, and snippets.

@MisterJimson
Forked from GroovinChip/README.md
Last active March 28, 2021 02:54
Show Gist options
  • Save MisterJimson/5ab49d44f5b74f90e7940f56d949cb12 to your computer and use it in GitHub Desktop.
Save MisterJimson/5ab49d44f5b74f90e7940f56d949cb12 to your computer and use it in GitHub Desktop.
My "Vintage bloc" usage style

My use of the classic (or "vintage") BLoC pattern (not to be confused with Felix Angelov's great bloc library) has evolved over the years. I do no use dedicated sinks for input and dedicated streams for output. Rather, I directly use rxdart's excellent BehaviorSubject directly. BehaviorSubject implements ValueStream, which itself implements Stream, so I found that I could reduce boilerplate a lot by doing this. Values can be directly added to, and read from, a BehaviorSubject. I then use provider to pass my services (I don't really think of them as "bloc"s any more) through my app. This gist provides a real example of how I currently use this pattern.

  • prefs_service.dart is where the service is defined.
  • main.dart shows how to initialize the service.
  • app.dart shows how I use MultiProvider to pass down my service. I always use MultiProvider rather than one single provider to allow for more services. I listen to the BehaviorSubject in my service to set the ThemeMode of my MaterialApp.
  • provided.dart shows how I use a mixin as a shortcut to my service.
  • theme_mode_switcher.dart shows how I use the mixin to get the service and update the value of the BehaviorSubject on user selection.

Disclaimer: I do not claim that this is the “right” way to do state management (there’s no such thing) and my style evolves over time. Additionally, there is nothing wrong with Felix's bloc ecosystem. Felix is great, he does amazing work for the Flutter community, and his work is of the highest caliber. I have nothing but respect for him. The discussion over the nature of BLoC pattern vs. his bloc library that pops up every now and then is not a criticism of him or his work. I hope very much that no one confuses it as such.

import 'package:flutter/material.dart';
import 'package:my_app/screens/home_screen.dart';
import 'package:my_app/services/prefs_service.dart';
import 'package:my_app/theme/app_themes.dart';
import 'package:provider/provider.dart';
class MyApp extends StatefulWidget {
const MyApp({
Key? key,
required this.prefsService,
}) : super(key: key);
final PrefsService prefsService;
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider<PrefsService>.value(value: widget.prefsService),
],
child: Observer(
builder: (context, snapshot) {
return MaterialApp(
title: 'MyApp',
theme: basicLight,
darkTheme: basicDark,
themeMode: widget.prefsService.themeMode,
home: HomeScreen(),
);
},
),
);
}
}
import 'package:gisthub/services/prefs_service.dart';
import 'package:flutter/material.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final prefsService = await PrefsService.init();
runApp(
MyApp(
prefsService: prefsService,
),
);
}
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:mobx/mobx.dart';
class PrefsService = _PrefsService with _$PrefsService;
/// User preferences service
abstract class _PrefsService with Store {
PrefsService._();
// Public initializer
static Future<PrefsService> init() async {
final service = PrefsService._();
await service._init();
return service;
}
// Private initializer
Future<void> _init() async {
preferences = await SharedPreferences.getInstance();
readThemeModePref();
}
late SharedPreferences preferences;
@observable
ThemeMode? themeMode;
/// Read user's ThemeMode preference from storage
///
/// Defaults to ThemeMode.system
@action
void readThemeModePref() {
String tm =
preferences.get('themeModePref') as String? ?? 'ThemeMode.system';
themeMode = ThemeMode.values.firstWhere((element) => element.toString() == tm);
}
/// Set user's ThemeMode preference
@action
Future<void> setThemeModePref(ThemeMode? themeMode) async {
await preferences.setString('themeModePref', '${themeMode.toString()}');
this.themeMode = themeMode;
}
}
import 'package:flutter/material.dart';
import 'package:my_app/services/prefs_service.dart';
import 'package:provider/provider.dart';
mixin Provided<T extends StatefulWidget> on State<T> {
PrefsService? _prefsService;
PrefsService get prefsService =>
_prefsService ??= Provider.of<PrefsService>(context, listen: false);
}
import 'package:flutter/material.dart';
import 'package:my_app/mixins/provided.dart';
/// A dialog for selecting which ThemeMode to use.
class ThemeSwitcherDialog extends StatefulWidget {
@override
_ThemeSwitcherDialogState createState() => _ThemeSwitcherDialogState();
}
class _ThemeSwitcherDialogState extends State<ThemeSwitcherDialog> with Provided {
void _onThemeSelection(ThemeMode? themeMode) {
prefsService.setThemeModePref(themeMode);
Navigator.of(context).pop();
}
@override
Widget build(BuildContext context) {
return Observer(builder: (_) {
SimpleDialog(
backgroundColor: Theme.of(context).canvasColor,
title: const Text('Change app theme'),
children: [
RadioListTile(
title: const Text('System theme'),
value: ThemeMode.system,
selected: prefsService.themeMode == ThemeMode.system ? true : false,
activeColor: Theme.of(context).accentColor,
groupValue: prefsService.themeMode,
onChanged: _onThemeSelection,
),
RadioListTile(
title: const Text('Light theme'),
value: ThemeMode.light,
selected: prefsService.themeMode == ThemeMode.light ? true : false,
activeColor: Theme.of(context).accentColor,
groupValue: prefsService.themeMode,
onChanged: _onThemeSelection,
),
RadioListTile(
title: const Text('Dark theme'),
value: ThemeMode.dark,
selected: prefsService.themeMode == ThemeMode.dark ? true : false,
activeColor: Theme.of(context).accentColor,
groupValue: prefsService.themeMode,
onChanged: _onThemeSelection,
),
],
),
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment