Created
March 30, 2023 18:12
-
-
Save marcguilera/f177bd025dad79baca08e3c93cbfd6d5 to your computer and use it in GitHub Desktop.
Flame + Riverpod
This file contains 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 '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); | |
} | |
} |
This file contains 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 '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