Skip to content

Commit

Permalink
feat: Integration of flame_riverpod (#2367)
Browse files Browse the repository at this point in the history
Previously discussed with @spydon and referenced in issue #2353,
flame_riverpod is to be integrated into the monorepo.
  • Loading branch information
markvideon authored Dec 1, 2023
1 parent 81930e2 commit 0c74560
Show file tree
Hide file tree
Showing 22 changed files with 1,083 additions and 0 deletions.
1 change: 1 addition & 0 deletions .github/.cspell/people_usernames.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ subosito # github.com/subosito
spydon # github.com/spydon
stpasha # github.com/stpasha
tavian # tavianator.com
videon # github.com/markvideon
wolfenrain # github.com/wolfenrain
xaha # github.com/xvrh
1 change: 1 addition & 0 deletions .github/.cspell/words_dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ proxying
ptero # short for pterodactyl
rebalance
redeclaration
refreshable
renderable
rescan
tappable
Expand Down
7 changes: 7 additions & 0 deletions doc/bridge_packages/bridge_packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ Replace FCS with the Oxygen Entity Component System.
Create interactive animations (bridge package for [Rive]).
:::

:::{package} flame_riverpod

A reactive caching and data-binding framework (bridge package for [Riverpod]).
:::

:::{package} flame_spine

Use Spine skeletal animations (bridge package for [Spine]).
Expand All @@ -71,6 +76,7 @@ Draw SVG files in Flutter (bridge package for [flutter_svg]).
[Forge2D]: https://github.com/flame-engine/forge2d
[Lottie]: https://pub.dev/packages/lottie
[Rive]: https://rive.app/
[Riverpod]: https://github.com/rrousselGit/riverpod
[Spine]: https://pub.dev/packages/spine_flutter
[Tiled]: https://www.mapeditor.org/
[flutter_svg]: https://github.com/dnfield/flutter_svg
Expand All @@ -88,6 +94,7 @@ flame_lottie <flame_lottie/flame_lottie.md>
flame_network_assets <flame_network_assets/flame_network_assets.md>
flame_oxygen <flame_oxygen/flame_oxygen.md>
flame_rive <flame_rive/flame_rive.md>
flame_riverpod <flame_riverpod/flame_riverpod.md>
flame_splash_screen <flame_splash_screen/flame_splash_screen.md>
flame_spine <flame_spine/flame_spine.md>
flame_svg <flame_svg/flame_svg.md>
Expand Down
46 changes: 46 additions & 0 deletions doc/bridge_packages/flame_riverpod/component.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Component


## ComponentRef

`ComponentRef` exposes Riverpod functionality to individual `Component`s, and is comparable to
`flutter_riverpod`'s `WidgetRef`.


## RiverpodComponentMixin

`RiverpodComponentMixin` manages the lifecycle of listeners on behalf of individual `Component`s.

`Component`s using this mixin must use `addToGameWidgetBuild` in their `onMount` method to add
listeners (e.g. `ref.watch` or `ref.listen`) *prior to* calling `super.onMount`, which manages the
staged listeners and disposes of them on the user's behalf inside `onRemove`.

```dart
class RiverpodAwareTextComponent extends PositionComponent
with RiverpodComponentMixin {
late TextComponent textComponent;
int currentValue = 0;
@override
void onMount() {
addToGameWidgetBuild(() {
ref.listen(countingStreamProvider, (p0, p1) {
if (p1.hasValue) {
currentValue = p1.value!;
textComponent.text = '$currentValue';
}
});
});
super.onMount();
add(textComponent = TextComponent(position: position + Vector2(0, 27)));
}
}
```


## RiverpodGameMixin

`RiverpodGameMixin` provides listeners from all components to the build method of the
`RiverpodAwareGameWidget`.
8 changes: 8 additions & 0 deletions doc/bridge_packages/flame_riverpod/flame_riverpod.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# flame_riverpod

```{toctree}
Overview <riverpod.md>
Component <component.md>
Widget <widget.md>
```

67 changes: 67 additions & 0 deletions doc/bridge_packages/flame_riverpod/riverpod.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# flame_riverpod


## Riverpod

[Riverpod](https://riverpod.dev/) is a reactive caching and data-binding
framework for Dart & Flutter.

In `flutter_riverpod`, widgets can be configured to rebuild when the state
of a provider changes.

When using Flame, we are interacting with components, which are *not* Widgets.

`flame_riverpod` provides the `RiverpodAwareGameWidget`, `RiverpodGameMixin`, and
`RiverpodComponentMixin` to facilitate managing state from `Provider`s in your Flame Game.


## Usage

You should use the `RiverpodAwareGameWidget` as your Flame `GameWidget`, the `RiverpodGameMixin`
mixin on your game that extends `FlameGame`, and the `RiverpodComponentMixin` on any components
interacting with Riverpod providers.

Subscriptions to a provider are managed in accordance with the lifecycle
of a Flame Component: initialization occurs when a Component is mounted, and disposal
occurs when a Component is removed.

By default, the `RiverpodAwareGameWidget` is rebuilt when
Riverpod-aware (i.e. using the `RiverpodComponentMixin`) components are mounted and when they are
removed.

```dart
/// An excerpt from the Example. Check it out!
class RefExampleGame extends FlameGame with RiverpodGameMixin {
@override
Future<void> onLoad() async {
await super.onLoad();
add(TextComponent(text: 'Flame'));
add(RiverpodAwareTextComponent());
}
}
class RiverpodAwareTextComponent extends PositionComponent
with RiverpodComponentMixin {
late TextComponent textComponent;
int currentValue = 0;
@override
void onMount() {
addToGameWidgetBuild(() {
ref.listen(countingStreamProvider, (p0, p1) {
if (p1.hasValue) {
currentValue = p1.value!;
textComponent.text = '$currentValue';
}
});
});
super.onMount();
add(textComponent = TextComponent(position: position + Vector2(0, 27)));
}
}
```

The order of operations in `Component.onMount` is important. The `RiverpodComponentMixin`
interacts with `RiverpodGameMixin` (inside of `RiverpodComponentMixin.onMount`) to co-ordinate
adding and removing listeners as the corresponding component is mounted and removed, respectively.
16 changes: 16 additions & 0 deletions doc/bridge_packages/flame_riverpod/widget.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Widget


## RiverpodAwareGameWidget

`RiverpodAwareGameWidget` is a GameWidget with a `State` object of type
`RiverpodAwareGameWidgetState`.

The required `GlobalKey` argument is used to provide `Component`s using `RiverpodComponentMixin`
access to `Provider`s via `RiverpodAwareGameWidgetState`.


## RiverpodAwareGameWidgetState

`RiverpodAwareGameWidgetState` performs the duties associated with the
`ConsumerStatefulElement` in `flutter_riverpod` and `GameWidgetState` in `flame`.
62 changes: 62 additions & 0 deletions packages/flame_riverpod/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
## 5.0.0

* New API with breaking changes. Added [RiverpodAwareGameWidget], [RiverpodGameMixin], [RiverpodComponentMixin]. See the example for details.

## 4.0.0+2

* Miscellaneous format post-processing on the files.

## 4.0.0+1

* Miscellaneous tidy-up of package internals.

## 4.0.0

* Made [WidgetRef] property on [ComponentRef] private. It should not be accessed directly.
* Removed the [riverpodAwar`eGameProvider]. If required, this is better handled at the application-level.

## 3.0.0

* Changes to focus on [FlameGame].
* [riverpodAwareGameProvider] now expects a [FlameGame].
* Removed the [HasComponentRef] on Game.
* Renamed [RiverpodComponentMixin] to [HasComponentRef]
* [HasComponentRef] now has a static setter for a WidgetRef. Components that use the new [HasComponentRef] mixin no
longer need to explicitly provide a [ComponentRef].
* Renamed the [WidgetRef] property on the [ComponentRef] to [widgetRef].
* Updated Example to reflect changes.
* Updated README to reflect changes.

## 2.0.0

* Pruned the public API, removing custom widget definitions (these have now been defined inside the example for
reference)
* Renamed [RiverpodAwareGameMixin] -> [HasComponentRef] to bring closer to the Flame 'house-style' for mixins.

## 1.1.0+2

* Another correction to README and example code. onMount should not call super.onLoad.

## 1.1.0+1

* Correction to README to reflect API change.

## 1.1.0

* Added [RiverpodComponentMixin] to handle disposing of [ProviderSubscription]s.
* Correction to the [RiverpodGameWidget] initializeGame constructor - param is now
[RiverpodAwareGameMixin Function (ref)] as originally intended.

## 1.0.0+1

* Reduced package description length.
* Ran dart format.

## 1.0.0

* Initial release.
* ComponentRef
* riverpodAwareGameProvider
* RiverpodAwareFlameGame
* RiverpodAwareGame
* RiverpodGameWidget
21 changes: 21 additions & 0 deletions packages/flame_riverpod/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2023 Blue Fire

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
78 changes: 78 additions & 0 deletions packages/flame_riverpod/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# flame_riverpod

[Riverpod](https://pub.dev/packages/flutter_riverpod) is a reactive caching and data-binding
framework for Dart & Flutter.

In `flutter_riverpod`, widgets can be configured to rebuild when the state
of a provider changes.

When using Flame, we are interacting with components, which are *not* Widgets.

`flame_riverpod` provides the `RiverpodAwareGameWidget`, `RiverpodGameMixin`, and
`RiverpodComponentMixin` to facilitate managing state from Providers in your Flame Game.


## Usage

You should use the `RiverpodAwareGameWidget` as your Flame `GameWidget`, the `RiverpodGameMixin`
mixin on your game that extends `FlameGame`, and the `RiverpodComponentMixin` on any components
interacting with Riverpod providers.

The full range of operations defined in Riverpod's `WidgetRef` definition are accessible from
components.

Subscriptions to a provider are managed in accordance with the lifecycle
of a Flame Component: initialization occurs when a Component is mounted, and disposal
occurs when a Component is removed. By default, the `RiverpodAwareGameWidget` is rebuilt when
Riverpod-aware (i.e. using the `RiverpodComponentMixin`) components are mounted and when they are
removed.

```dart
/// An excerpt from the Example. Check it out!
class RefExampleGame extends FlameGame with RiverpodGameMixin {
@override
Future<void> onLoad() async {
await super.onLoad();
add(TextComponent(text: 'Flame'));
add(RiverpodAwareTextComponent());
}
}
class RiverpodAwareTextComponent extends PositionComponent
with RiverpodComponentMixin {
late TextComponent textComponent;
int currentValue = 0;
/// [onMount] should be used over [onLoad] to initialize subscriptions,
/// cancellation is handled for the user inside [onRemove],
/// which is only called if the [Component] was mounted.
///
/// [RiverpodComponentMixin.addToGameWidgetBuild] **must** be invoked in
/// your Component **before** [RiverpodComponentMixin.onMount] in order to
/// have the provided function invoked on
/// [RiverpodAwareGameWidgetState.build].
///
/// From `flame_riverpod` 5.0.0, [WidgetRef.watch], is also accessible from
/// components.
@override
void onMount() {
addToGameWidgetBuild(() {
ref.listen(countingStreamProvider, (p0, p1) {
if (p1.hasValue) {
currentValue = p1.value!;
textComponent.text = '$currentValue';
}
});
});
super.onMount();
add(textComponent = TextComponent(position: position + Vector2(0, 27)));
}
}
```


## Credits

[Mark Videon](https://markvideon.dev) for the initial groundwork and implementation.
1 change: 1 addition & 0 deletions packages/flame_riverpod/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include: package:flame_lint/analysis_options.yaml
30 changes: 30 additions & 0 deletions packages/flame_riverpod/example/.metadata
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.

version:
revision: "efbf63d9c66b9f6ec30e9ad4611189aa80003d31"
channel: "stable"

project_type: app

# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
- platform: macos
create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31

# User provided section

# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
10 changes: 10 additions & 0 deletions packages/flame_riverpod/example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# flame_riverpod_example

The example consists of a very simple FlameGame with a custom
Component, updated alongside a comparable Flutter widget.

Both the Component and the Widget depend on a `StreamProvider`
that counts upwards indefinitely.

Both a Flame Component and a Flutter Text Widget update in real-time from
the same data source.
1 change: 1 addition & 0 deletions packages/flame_riverpod/example/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include: package:flame_lint/analysis_options.yaml
Loading

0 comments on commit 0c74560

Please sign in to comment.