Skip to content

Instantly share code, notes, and snippets.

@lukepighetti
Last active April 18, 2023 18:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lukepighetti/8ba7adb462f7967bfcc63588313a962e to your computer and use it in GitHub Desktop.
Save lukepighetti/8ba7adb462f7967bfcc63588313a962e to your computer and use it in GitHub Desktop.
A crusty but nice way to deal with ChangeNotifier stores in dart
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '/app/store_architecture.dart';
import '/feedback/feedback_store.dart';
import '/par/par_store.dart';
import '/settings/settings_binding_observer.dart';
import '/settings/settings_store.dart';
import '/sound/sound_service.dart';
import '/telemetry/telemetry_store.dart';
class AppStore extends RootStore {
final navigatorKey = GlobalKey<NavigatorState>();
late final FirebaseAnalytics analytics;
late final SharedPreferences prefs;
late final SoundService sound;
late final FeedbackStore feedback;
late final ParStore par;
late final SettingsStore settings;
late final TelemetryStore telemetry;
late final SettingsBindingObserver _settingsBindingObserver;
@override
Future<void> initState() async {
/// Services
analytics = FirebaseAnalytics.instance;
prefs = await SharedPreferences.getInstance();
sound = SoundService();
await sound.initState();
/// Stores
feedback = FeedbackStore(this);
par = ParStore(this);
settings = SettingsStore(this);
telemetry = TelemetryStore(this);
registerChildren([
feedback,
par,
settings,
telemetry,
]);
_settingsBindingObserver = SettingsBindingObserver(settings);
_settingsBindingObserver.setup();
await super.initState();
}
}
extension AppStoreBuildContextX on BuildContext {
/// Read the store once, doesn't subscribe a BuildContext to update.
AppStore readStore() => read<AppStore>();
/// Watch the store by subscribing to a BuildContext.
AppStore watchStore() => watch<AppStore>();
/// Select a piece of the store, subscribing a BuildContext to update only
/// when the underlying field changes. Uses `==` operator to determine changes
T selectField<T>(T Function(AppStore s) selector) => select(selector);
}
import 'package:flutter/foundation.dart';
/// A [ChangeNotifier] with built in fields for [initState], [initialized] and [setState].
///
/// Contains multiple [ChildStore]s
abstract class Store extends ChangeNotifier {
/// Initialize this store.
///
/// Must call super after initialization is complete.
@mustCallSuper
Future<void> initState() async {
_initialized = true;
notifyListeners();
}
/// If this [ChildStore] is fully initialized
bool get initialized => _initialized;
bool _initialized = false;
/// Alternative to notifyListeners. Matches [StatefulWidget] semantics.
void setState(Function fn) {
fn();
notifyListeners();
}
}
/// A [ChangeNotifier] with built in fields for [RootStore], [initState],
/// [initialized] and [setState].
abstract class ChildStore<T extends Store> extends Store {
ChildStore(this.store);
/// The parent store that contains this store.
final T store;
}
/// A [Store] that has a [registerStores]
/// method which connects these child stores to this [RootStore],
/// initializes them and then disposes of the subscription.
abstract class RootStore extends Store {
List<Store> _children = [];
/// Register stores with this [RootStore].
///
/// The root store will subscribe to all childrens
/// [notifyListeners] calls, initialize them, and dispose
/// of the subscription.
void registerChildren(List<Store> children) {
_children = children;
}
@override
Future<void> initState() async {
if (_children.isEmpty) {
debugPrint(
"WARNING: $this has no _children. Typically a RootStore would have _children stores.",
);
}
/// Connect the root store's [notifyListeners] method to
/// all child stores [notifyListeners] method.
for (var e in _children) {
e.addListener(notifyListeners);
}
/// Await all child store's [initState]
await Future.wait(_children.map((e) => e.initState()));
return super.initState();
}
@override
void dispose() {
/// Dispose all [notifyListeners] handlers.
for (var e in _children) {
e.removeListener(notifyListeners);
e.dispose();
}
super.dispose();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment