Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Integration of flame_riverpod #2367

Merged
merged 49 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
98861eb
Committing flame_riverpod project files
markvideon Feb 1, 2023
855749f
Added widget test for the example.
markvideon Feb 25, 2023
c2e93b5
Ran `dart format . --set-exit-if-changed`
markvideon Feb 25, 2023
1eb5b84
Removed platform folders from the example.
markvideon Feb 26, 2023
185482f
Added visibleForTesting annotation to the lifecycle field of [Compone…
markvideon Feb 26, 2023
127810b
Update packages/flame_riverpod/README.md
markvideon Feb 27, 2023
e0672cf
Removed visibleForTesting annotation - no longer calling lifecycle ma…
markvideon Feb 27, 2023
d295958
Merge branch 'main' of github.com:markvideon/flame
markvideon Feb 27, 2023
461aba3
Changed LICENSE to MIT
markvideon Feb 27, 2023
dad9a27
Updated pubspec for flame_riverpod and the example project as per cha…
markvideon Feb 27, 2023
7b09c50
Removed example project .gitignore and .metadata files
markvideon Feb 27, 2023
1194a80
Made the list of subscriptions final as per comment
markvideon Feb 27, 2023
5a66380
Refactor of unitialisedGame -> unitializedGame that should have been …
markvideon Feb 27, 2023
bbd0de9
Changes to widgetTest to remove need to manually trigger lifecycle ev…
markvideon Feb 27, 2023
44b0174
Added suggested self-credit copy to package README
markvideon Feb 27, 2023
eb3242a
Removed access of hasPendingLifecycleEvents inside the example widget…
markvideon Feb 27, 2023
a3c48aa
Americanised name: RiverpodGameWidget constructor (initialiseWithGame…
markvideon Feb 27, 2023
26c1a0a
Updated description of flame_riverpod to provide context.
markvideon Feb 27, 2023
da49352
More Americanisation of the word initialise
markvideon Feb 27, 2023
9ff8c05
Updated READMEs
markvideon Feb 27, 2023
f3f241d
Adjustments based on feedback in Markdown, Format github actions
markvideon Feb 27, 2023
80da27c
Addressed feedback from markdown lint github action
markvideon Feb 27, 2023
f385afa
Merge branch 'main' into main
markvideon Feb 28, 2023
f8d7270
Update packages/flame_riverpod/example/README.md
markvideon Feb 28, 2023
dc1176c
Update packages/flame_riverpod/example/pubspec.yaml
markvideon Feb 28, 2023
8fa32d5
Update packages/flame_riverpod/example/pubspec.yaml
markvideon Feb 28, 2023
b29dac7
chore: Added newlines to EOF for pubspec and analysis_options files.
markvideon Feb 28, 2023
8c2d5db
doc: Added dartdoc comments to HasComponentRef.
markvideon Feb 28, 2023
8965e51
fix: Only reference in scope identifiers in doc comments, as per inve…
markvideon Feb 28, 2023
bde27e7
Update packages/flame_riverpod/example/pubspec.yaml
markvideon Mar 1, 2023
2950ad7
Update packages/flame_riverpod/pubspec.yaml
markvideon Mar 1, 2023
64849a9
Update packages/flame_riverpod/example/pubspec.yaml
markvideon Mar 1, 2023
d6027fd
Merge branch 'flame-engine:main' into main
markvideon Nov 30, 2023
7e0de63
flame_riverpod v5
markvideon Nov 30, 2023
7bb44b1
Added link to flutter_riverpod within README
markvideon Nov 30, 2023
1aec2d1
Chore: adjusted end of line spacing in README, renamed test/test.dart…
markvideon Nov 30, 2023
7931d5d
Chore: Addressed markdown lint warnings.
markvideon Nov 30, 2023
838bd23
Chore: Added words to cspell
markvideon Nov 30, 2023
76e6e7c
Chore: Moved name to people_usernames.txt from words_dictionary.txt
markvideon Nov 30, 2023
189a001
Removed example gitignore
markvideon Nov 30, 2023
83b7457
Chore: Modified flame_riverpod example to use a version rather than r…
markvideon Nov 30, 2023
7d4783d
Docs: Dartdoc for RiverpodAwareGameWidget
markvideon Nov 30, 2023
fdf8273
Update packages/flame_riverpod/example/lib/main.dart
markvideon Nov 30, 2023
b1bcc27
docs: Bridge packages documentation for flame_riverpod
markvideon Dec 1, 2023
b78ed9c
fix: Trailing space deteced by markdownlint
markvideon Dec 1, 2023
3bd0534
Merge branch 'main' of github.com:markvideon/flame
markvideon Dec 1, 2023
52d2a6d
Update doc/bridge_packages/flame_riverpod/flame_riverpod.md
markvideon Dec 1, 2023
4074151
Update doc/bridge_packages/flame_riverpod/riverpod.md
markvideon Dec 1, 2023
d77a620
Merge branch 'main' into main
spydon Dec 1, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions packages/flame_riverpod/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
## 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.
76 changes: 76 additions & 0 deletions packages/flame_riverpod/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# flame_riverpod

Riverpod is a reactive caching and data-binding framework for Dart & Flutter.
markvideon marked this conversation as resolved.
Show resolved Hide resolved

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 `ComponentRef` and `HasComponentRef` to
facilitate managing state from Providers in your Flame Game.


## Usage

Your Widget that extends `FlameGame` should use the HasComponentRef mixin,
markvideon marked this conversation as resolved.
Show resolved Hide resolved
and accept a WidgetRef inside its constructor, and should be wrapped in a
markvideon marked this conversation as resolved.
Show resolved Hide resolved
ConsumerStatefulWidget, as per the example.
markvideon marked this conversation as resolved.
Show resolved Hide resolved

The `listen` method can be used within any Component that uses the
HasComponentMixin to subscribe to updates from a provider.

Alternatively, you could use `ref.read` as you would elsewhere when consuming `flutter_riverpod`.

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.

```dart

/// An excerpt from the Example. Check it out!
class RefExampleGame extends FlameGame with HasComponentRef {
RefExampleGame(WidgetRef ref) {
HasComponentRef.widgetRef = ref;
}

@override
onLoad() async {
await super.onLoad();
add(TextComponent(text: 'Flame'));
add(RiverpodAwareTextComponent());
}
}

class RiverpodAwareTextComponent extends PositionComponent
with HasComponentRef {

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.
@override
void onMount() {
super.onMount();
add(textComponent = TextComponent(position: position + Vector2(0, 27)));

// "Watch" a provider using [listen] from the [HasComponentRef] mixin.
// Watch is not exposed directly as this would rebuild the ancestor that
// exposes the [WidgetRef] unnecessarily.
listen(countingStreamProvider, (p0, p1) {
if (p1.hasValue) {
currentValue = p1.value!;
textComponent.text = '$currentValue';
}
});
}
}

```
spydon marked this conversation as resolved.
Show resolved Hide resolved


## 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
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
158 changes: 158 additions & 0 deletions packages/flame_riverpod/example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import 'dart:async';

import 'package:flame/components.dart' hide Timer;
import 'package:flame/game.dart';
import 'package:flame_riverpod/flame_riverpod.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final countingStreamProvider = StreamProvider<int>((ref) {
return Stream.periodic(const Duration(seconds: 1), (inc) => inc);
});

/// Simple provider that returns a [FlameGame] instance.
final riverpodAwareGameProvider =
StateNotifierProvider<RiverpodAwareGameNotifier, FlameGame?>((ref) {
return RiverpodAwareGameNotifier();
});

/// Simple [StateNotifier] that holds the current [FlameGame] instance.
class RiverpodAwareGameNotifier extends StateNotifier<FlameGame?> {
RiverpodAwareGameNotifier() : super(null);

void set(FlameGame candidate) {
state = candidate;
}
}

void main() {
runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Expanded(child: FlutterCountingComponent()),
Expanded(
child: RiverpodGameWidget.initializeWithGame(
uninitializedGame: RefExampleGame.new,
),
)
],
),
);
}
}

class FlutterCountingComponent extends ConsumerWidget {
const FlutterCountingComponent({super.key});

@override
Widget build(BuildContext context, WidgetRef ref) {
final textStyle = Theme.of(context)
.textTheme
.headlineSmall
?.copyWith(color: Colors.white);

final stream = ref.watch(countingStreamProvider);
return Material(
color: Colors.transparent,
child: Column(
children: [
Text('Flutter', style: textStyle),
stream.when(
data: (value) => Text('$value', style: textStyle),
error: (error, stackTrace) => Text('$error', style: textStyle),
loading: () => Text('Loading...', style: textStyle),
)
],
),
);
}
}

class RefExampleGame extends FlameGame with HasComponentRef {
markvideon marked this conversation as resolved.
Show resolved Hide resolved
RefExampleGame(WidgetRef ref) {
// Note: WidgetRef is not stored and is used to build a [ComponentRef],
// a wrapper around it.
HasComponentRef.widgetRef = ref;
}
markvideon marked this conversation as resolved.
Show resolved Hide resolved

@override
Future<void> onLoad() async {
await super.onLoad();
add(TextComponent(text: 'Flame'));
add(RiverpodAwareTextComponent());
}
}

class RiverpodGameWidget extends ConsumerStatefulWidget {
const RiverpodGameWidget.readFromProvider({super.key})
: uninitializedGame = null;
const RiverpodGameWidget.initializeWithGame({
super.key,
required this.uninitializedGame,
});

final FlameGame Function(WidgetRef ref)? uninitializedGame;

@override
ConsumerState<RiverpodGameWidget> createState() => _RiverpodGameWidgetState();
}

class _RiverpodGameWidgetState extends ConsumerState<RiverpodGameWidget> {
@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (widget.uninitializedGame is FlameGame Function(WidgetRef ref)) {
markvideon marked this conversation as resolved.
Show resolved Hide resolved
ref
.read(riverpodAwareGameProvider.notifier)
.set(widget.uninitializedGame!(ref));
markvideon marked this conversation as resolved.
Show resolved Hide resolved
}
});
super.initState();
}

@override
Widget build(BuildContext context) {
final game = ref.watch(riverpodAwareGameProvider);

if (game is! Game) {
return Container();
}

return GameWidget(game: game!);
}
}

class RiverpodAwareTextComponent extends PositionComponent
with HasComponentRef {
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.
markvideon marked this conversation as resolved.
Show resolved Hide resolved
markvideon marked this conversation as resolved.
Show resolved Hide resolved
@override
void onMount() {
super.onMount();
markvideon marked this conversation as resolved.
Show resolved Hide resolved
add(textComponent = TextComponent(position: position + Vector2(0, 27)));

// "Watch" a provider using [listen] from the [HasComponentRef] mixin.
// Watch is not exposed directly as this would rebuild the ancestor that
// exposes the [WidgetRef] unnecessarily.
listen(countingStreamProvider, (p0, p1) {
markvideon marked this conversation as resolved.
Show resolved Hide resolved
if (p1.hasValue) {
currentValue = p1.value!;
textComponent.text = '$currentValue';
}
});
}
}
20 changes: 20 additions & 0 deletions packages/flame_riverpod/example/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: flame_riverpod_example
description: Showcasing the flame_riverpod functionality.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
markvideon marked this conversation as resolved.
Show resolved Hide resolved
version: 1.0.0+1
environment:
sdk: '>=2.18.5 <3.0.0'
markvideon marked this conversation as resolved.
Show resolved Hide resolved
dependencies:
flame: ^1.6.0
flame_riverpod: ^4.0.0+2
flutter:
sdk: flutter
flutter_riverpod: ^2.1.3

dev_dependencies:
flame_lint: ^0.2.0
flutter_test:
sdk: flutter

flutter:
uses-material-design: true
Loading