-
-
Notifications
You must be signed in to change notification settings - Fork 932
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: adding has mounted to component #1418
Changes from 4 commits
09d2ce8
c750a56
a070192
10a91f7
bc50a77
b995d88
ba92621
d383ebd
b94828b
63d8515
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
import 'dart:async'; | ||
import 'dart:collection'; | ||
|
||
import 'package:flutter/painting.dart'; | ||
|
@@ -53,6 +54,8 @@ class Component { | |
bool get hasChildren => _children?.isNotEmpty ?? false; | ||
ComponentSet? _children; | ||
|
||
Completer<void>? _mountCompleter; | ||
|
||
@protected | ||
_LifecycleManager get lifecycle => _lifecycleManager ??= _LifecycleManager(); | ||
_LifecycleManager? _lifecycleManager; | ||
|
@@ -203,6 +206,17 @@ class Component { | |
/// ``` | ||
void onMount() {} | ||
|
||
/// A future that will complete once the component is mounted on its parent | ||
Future<void> get hasMounted { | ||
if (isMounted) { | ||
return Future.value(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice solution! |
||
} | ||
|
||
_mountCompleter ??= Completer<void>(); | ||
|
||
return _mountCompleter!.future; | ||
} | ||
|
||
/// This method is called periodically by the game engine to request that your | ||
/// component updates itself. | ||
/// | ||
|
@@ -258,6 +272,7 @@ class Component { | |
/// Changes the current parent for another parent and prepares the tree under | ||
/// the new root. | ||
void changeParent(Component component) { | ||
_mountCompleter = null; | ||
parent?.remove(this); | ||
nextParent = component; | ||
} | ||
|
@@ -403,6 +418,7 @@ class Component { | |
if (existingChild || _state == LifecycleState.removed) { | ||
onGameResize(findGame()!.canvasSize); | ||
} | ||
_mountCompleter?.complete(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Set it to null here? |
||
onMount(); | ||
_state = LifecycleState.mounted; | ||
if (!existingChild) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,6 +47,52 @@ void main() { | |
); | ||
}); | ||
|
||
flameGame.test('component hasMounted completes', (game) async { | ||
erickzanardo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
final component = _MyComponent(); | ||
await game.add(component); | ||
final hasMounted = component.hasMounted; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I presume it is not possible to await for this future, since it cannot complete without the game loop running its course -- hence this would become an infinite cycle. Perhaps it would be useful to have at least some test where we await There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I double checked and the future does completes, otherwise the test would fail. I changed to expectLater to be sure, but even the original code was working. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But can you do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, you can await it, the I could change my test to make use of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The point is, right now we have no tests to check whether this future is actually awaitable. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How come? This test is doing exactly that 🤔 |
||
|
||
await game.ready(); | ||
|
||
return expectLater(hasMounted, completes); | ||
}); | ||
|
||
flameGame.test( | ||
'component hasMounted completes even after the ' | ||
'component is already mounted', | ||
(game) async { | ||
final component = _MyComponent(); | ||
await game.add(component); | ||
await game.ready(); | ||
|
||
final hasMounted = component.hasMounted; | ||
|
||
return expectLater(hasMounted, completes); | ||
}, | ||
); | ||
|
||
flameGame.test( | ||
'component hasMounted completes when changing parent', | ||
(game) async { | ||
final parent = _MyComponent('parent'); | ||
final child = _MyComponent('child'); | ||
parent.add(child); | ||
game.add(parent); | ||
|
||
var hasMounted = child.hasMounted; | ||
await game.ready(); | ||
|
||
await expectLater(hasMounted, completes); | ||
|
||
child.changeParent(game); | ||
hasMounted = child.hasMounted; | ||
game.update(0); | ||
await game.ready(); | ||
|
||
await expectLater(hasMounted, completes); | ||
}, | ||
); | ||
|
||
// Obsolete scenario, when we used to have a separate "prepare" stage | ||
flameGame.test('parent prepares the component', (game) async { | ||
final parent = _MyComponent('parent'); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know why, but
hasMounted
doesn't convince me as the method name. If I ever read this I'll expect to get a boolean value from it not aFuture<void>
. @erickzanardo suggestedmounted
which I somewhat preferred overhasMounted
.In addition, I have a question on why do we think that using a
Completer
is better than registering callbacks (for example,whenMounted(callback)
)? Is this to avoid callback hell? Do we highly recommend always awaiting forhasMounted
, hence theFuture
type? or do we also suggest tounawait
if necessary?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the
mounted
name! @spydon what do you think?About the future vs callback, I think it have a nicer API, and actually, by using a future, we can let the user use a callback if they prefer, by doing
component.mounted.wheComplete(fn)
, so we can have the best of both worlds.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@erickzanardo true. Missed that API!
I think it's nice if we have it as
mounted
. I love how it reads:component.mounted.whenComplete
and
await component.mounted
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
component.mounted
still sounds alot like a boolean property, similar to.isMounted
. Maybe a better name would be.whenMounted
: