Skip to content

Instantly share code, notes, and snippets.

@marcguilera
Created March 30, 2023 18:12
Show Gist options
  • Save marcguilera/f177bd025dad79baca08e3c93cbfd6d5 to your computer and use it in GitHub Desktop.
Save marcguilera/f177bd025dad79baca08e3c93cbfd6d5 to your computer and use it in GitHub Desktop.
Flame + Riverpod
import 'dart:async';
import 'package:flame/components.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'component_scope.dart';
/// {@template component_ref}
/// Provides access to riverpod providers in the [Component] tree.
/// Equivalent to [WidgetRef].
/// {@endtemplate}
abstract class ComponentRef {
void listen<T>(
ProviderListenable<T> provider,
void Function(T? previous, T next) listener, {
void Function(Object error, StackTrace stackTrace)? onError,
bool fireImmediately,
});
bool exists(ProviderBase<Object?> provider);
T read<T>(ProviderListenable<T> provider);
T refresh<T>(Refreshable<T> provider);
void invalidate(ProviderOrFamily provider);
}
/// Mixin which provides access to the [ComponentRef] to a [Component].
/// This should be used from lifecycle callbacks and is not available
/// in the component constructor.
mixin HasComponentRef on Component {
final _ref = _ComponentRef()..priority = -1;
/// {@macro component_ref}
ComponentRef get ref => _ref;
@override
@mustCallSuper
FutureOr<void> onLoad() => add(_ref);
}
class _ComponentRef extends Component implements ComponentRef {
final _subscriptions = <ProviderSubscription<Object?>>[];
ProviderContainer? _container;
ProviderContainer get container => _container ?? _update();
@override
FutureOr<void> onLoad() async {
await super.onLoad();
_update();
}
@override
void onMount() {
super.onMount();
_update();
}
@override
void onRemove() {
_dispose();
super.onRemove();
}
@override
bool exists(ProviderBase<Object?> provider) {
return container.exists(provider);
}
@override
void invalidate(ProviderOrFamily provider) {
invalidate(provider);
}
@override
void listen<T>(
ProviderListenable<T> provider,
void Function(T? previous, T next) listener, {
void Function(Object error, StackTrace stackTrace)? onError,
bool fireImmediately = false,
}) {
final sub = container.listen(
provider,
listener,
onError: onError,
fireImmediately: fireImmediately,
);
_subscriptions.add(sub);
}
@override
T read<T>(ProviderListenable<T> provider) {
return container.read(provider);
}
@override
T refresh<T>(Refreshable<T> provider) {
return container.refresh(provider);
}
ProviderContainer _update() {
final parent = ComponentScope.containerOf(this);
if (_container != parent) {
_dispose();
}
return _container = parent;
}
void _dispose() {
_container = null;
final copy = _subscriptions.toList();
for (final subscription in copy) {
subscription.close();
}
_subscriptions.removeRange(0, copy.length);
}
}
import 'dart:async';
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
/// Mixin representing a [Component] which has access to a
/// [ProviderContainer].
abstract class ContainerOwner extends Component {
/// The [ProviderContainer] currently in scope.
ProviderContainer get container;
}
/// {@template uncontrolled_component_scope}
/// Provides access to a [ProviderContainer] down the
/// flame [Component] tree without handling its lifecycle.
/// Equivalent to the [UncontrolledProviderScope].
/// {@endtemplate}
class UncontrolledComponentScope extends Component implements ContainerOwner {
/// {@macro uncontrolled_component_scope}
UncontrolledComponentScope({
required this.container,
super.children,
super.priority,
});
@override
final ProviderContainer container;
}
/// {@template component_scope}
/// Provides access to a [ProviderContainer] down the
/// flame [Component] tree handling its lifecycle.
/// Equivalent to [ProviderScope].
/// {@endtemplate}
class ComponentScope extends Component implements ContainerOwner {
/// {@macro component_scope}
ComponentScope({
ProviderContainer? parent,
this.overrides = const [],
this.observers = const [],
super.children,
super.priority,
}): _fixedParentContainer = parent {
_createContainer(parentContainer: parent);
}
final List<Override> overrides;
final List<ProviderObserver> observers;
final ProviderContainer? _fixedParentContainer;
ProviderContainer? _parentContainer;
late ProviderContainer _container;
@override
ProviderContainer get container => _container;
/// Gets the nearest [ProviderContainer]. Tries to get it
/// from the ancestors or from the [BuildContext] in the game.
static ProviderContainer containerOf(Component component) {
final container = component.findContainer();
if (container == null) {
throw StateError('No ComponentScope found');
}
return container;
}
@override
@mustCallSuper
FutureOr<void> onLoad() async {
await super.onLoad();
_mount();
}
@override
@mustCallSuper
void onMount() {
super.onMount();
_mount();
}
@override
@mustCallSuper
void onRemove() {
_container.dispose();
super.onRemove();
}
void _mount() {
final parentContainer = _fixedParentContainer ?? parent?.findContainer();
if (_parentContainer != parentContainer) {
_createContainer(container: _container, parentContainer: parentContainer);
}
}
void _createContainer({
ProviderContainer? container,
ProviderContainer? parentContainer,
}) {
container?.dispose();
_container = ProviderContainer(
parent: parentContainer,
overrides: overrides,
observers: observers,
);
}
}
extension on Component {
ProviderContainer? findContainer() {
for (final current in ancestors(includeSelf: true)) {
if (current is ContainerOwner) {
return current.container;
} else if (current is FlameGame) {
final context = current.buildContext;
if (context != null) {
return ProviderScope.containerOf(context);
}
}
}
return null;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment