Last active
April 18, 2023 18:23
-
-
Save lukepighetti/8ba7adb462f7967bfcc63588313a962e to your computer and use it in GitHub Desktop.
A crusty but nice way to deal with ChangeNotifier stores in dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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