-
Notifications
You must be signed in to change notification settings - Fork 9.8k
[url_launcher] Fixed call to onPlatformViewCreated after dispose #5431
Conversation
which caused an error, since it calls setState().
It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!). If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix? Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
@ditman Ping on this review? |
I'm about to get to it; thanks for the ping! (@mdebbar is also back, he might want to take a look as well!) |
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.
The fix looks good, thanks for the contribution!
I only have a small suggestion and then the PR should be good to go (if @ditman also thinks so).
Future<void> _asyncInit(PlatformViewCreationParams params) async { | ||
try { | ||
await _initialize(); | ||
if (context == null) { |
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 suggest keeping context
final and adding a new field _isDisposed
:
if (context == null) { | |
if (_isDisposed) { |
@@ -263,14 +274,29 @@ class LinkViewController extends PlatformViewController { | |||
} | |||
|
|||
@override | |||
Future<void> dispose() async { | |||
Future<void> dispose() { | |||
context = null; |
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.
context = null; | |
_isDisposed = true; |
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 kind of like the context being nullable (despite the $billion mistake), since it makes people aware that it may not be safe to access it.
I went over it again and realized the context wasn't actually used anywhere, so I removed it. I also realized that the element need not be late initialized, so there'd be no need for _isInitialized and the asserts, and so with nothing nullable, I added an _isDisposed as you suggest 😄
These three steps are all required before the PR can land |
92b7f7b
to
a640546
Compare
1ce7cf3
to
b4359de
Compare
b4359de
to
c49ce9b
Compare
@stuartmorgan: didn't want to bump the version prematurely. I've bumped it from 2.0.11 to 2.0.12, hope that's fine with you. Worse, I have no idea as to how to test it, except for the updated example app. And I don't see test-exempt covering this case 😄 |
BTW, I was struggling a bit with dependencies. It seems to me that the tests in the example app isn't actually testing the code in the repo, rather it's testing the code on pub.dev. Or am I missing something? Not used to this mono-repo thing... The best solution I could think of would be dependency overrides in the example project, but that gets rejected by one of the github checks. |
The example app is not itself an automated test. @mdebbar or @ditman may be able to advise on how to write a widget test for this scenario.
I'm not sure how you came to that conclusion; can you elaborate on why you think that?
Your changes are all to a single package, so it's not clear to me what the relevance of the monorepo is here. What is the issue? |
Not concluding; it just seems to me that way 😄 The tests live in the example package which references url_launcer from |
|
That was me. I wanted to use the |
I managed to make a test out of the scraps of the example app, that was failing without the changes to the |
85c83d1
to
7b5e35a
Compare
I'm not sure exactly what these comments mean, but you are welcome to add anything from within the package itself (i.e., that doesn't use things from a higher layer in the stack) to make the example useful for testing purposes, since that's what implementation package examples are for. |
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.
This looks good. I saw the issue happening with the test (run manually) but couldn't get it to fail either. After the fix, the exception seems to not be thrown.
I have a small question about the unawaited _asyncInit
, and some other details, but in general this seems good!
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.
(Another quick comment)
'viewType': linkViewType, | ||
}); | ||
if (_isDisposed) { | ||
return; |
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.
Can this disposed check be performed before actually asking the framework to create a platform_view?
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 think the whole point of this fix is to check after the await, sort of like you do when do if (!mounted) return;
after awaits in stateful widget methods. Besides, I can't see how _isDisposed
can be true before the await.
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 can't see how
_isDisposed
can be true before the await.
@kristoffer-zliide the lifecycle of Flutter Widgets and platform views is somewhat separate. In this code, you're adding elements to the DOM (code) and then checking if the Widget has been disposed to then not call onPlatformViewCreated.
I don't think you need to modify the DOM at all, or call "create" on a "platform_view" if the Widget that is being initialized has been previously marked as disposed. That's why I asked you to move the isDisposed
check above the invokeMethod
, to exit as early as possible.
In fact, if onPlatformViewCreated
could be called multiple times earlier, this should be complaining about viewId
being already created. (Is the reportError
swallowing the exception on the web? Looking.)
(Note: this LinkViewController was originally modeled to look like the class _HtmlElementViewController
, here, so everything used initialized
instead of disposed
)
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 appreciate the desire to skip anything that would need to be awaited, but I don't quite follow anything else you're writing. With the refactoring done in this PR, I don't think I need to read more than 15 lines of code in the Link.dart file to see that the check wouldn't do anything before the await, since _asyncInitialize
is only called on newly constructed LinkViewController
s which have _isDisposed
set to false. Moving the check up also implies not doing it after, which was the whole point of the PR. But maybe I missed something in the refactoring?
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'm just trying to understand what's flutter doing here, because I don't think we should be getting an "init" on something that has been already "disposed".
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.
The framework seems to be disposing of the link while the await invokeMethod 'create'
is executing, so when it's time to call onPlatformViewCreated
, viewId
is already disposed.
Something like:
Disposed? false - before await create 428 - 918280561
!!! - dispose 428 - 918280561
Disposed? true - after await create 428 - 918280561
Exception! Kaboom 428 - 918280561
@kristoffer-zliide, I've simplified your PR a little bit here: I used your same test, and I think everything is working as expected. I don't know if you're testing this locally on your project, but you can use my branch from github to see if it solves your actual issue! Thanks for helping me understand this bug! |
Don't call onPlatformViewCreated after dispose, since it calls setState().
Fixes #102741
Pre-launch Checklist
dart format
.)[shared_preferences]
pubspec.yaml
with an appropriate new version according to the pub versioning philosophy, or this PR is exempt from version changes.CHANGELOG.md
to add a description of the change, following repository CHANGELOG style.///
).If you need help, consider asking for advice on the #hackers-new channel on Discord.