-
Notifications
You must be signed in to change notification settings - Fork 9.8k
[url_launcher] Fixed call to onPlatformViewCreated after dispose #5431
Changes from all commits
8fb082e
a640546
3b09ff5
0354b1d
c49ce9b
00f0f2a
7b5e35a
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,7 @@ | ||
## 2.0.12 | ||
|
||
* Fixed call to `setState` after dispose. | ||
|
||
## 2.0.11 | ||
|
||
* Minor fixes for new analysis options. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -76,7 +76,7 @@ class WebLinkDelegateState extends State<WebLinkDelegate> { | |
child: PlatformViewLink( | ||
viewType: linkViewType, | ||
onCreatePlatformView: (PlatformViewCreationParams params) { | ||
_controller = LinkViewController.fromParams(params, context); | ||
_controller = LinkViewController.fromParams(params); | ||
return _controller | ||
..setUri(widget.link.uri) | ||
..setTarget(widget.link.target); | ||
|
@@ -100,7 +100,7 @@ class WebLinkDelegateState extends State<WebLinkDelegate> { | |
/// Controls link views. | ||
class LinkViewController extends PlatformViewController { | ||
/// Creates a [LinkViewController] instance with the unique [viewId]. | ||
LinkViewController(this.viewId, this.context) { | ||
LinkViewController(this.viewId) : _element = _makeElement(viewId) { | ||
if (_instances.isEmpty) { | ||
// This is the first controller being created, attach the global click | ||
// listener. | ||
|
@@ -113,14 +113,28 @@ class LinkViewController extends PlatformViewController { | |
/// platform view [params]. | ||
factory LinkViewController.fromParams( | ||
PlatformViewCreationParams params, | ||
BuildContext context, | ||
) { | ||
final int viewId = params.id; | ||
final LinkViewController controller = LinkViewController(viewId, context); | ||
controller._initialize().then((_) { | ||
) => | ||
LinkViewController(params.id).._asyncInitialize(params); | ||
ditman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Future<void> _asyncInitialize(PlatformViewCreationParams params) async { | ||
try { | ||
await SystemChannels.platform_views | ||
.invokeMethod<void>('create', <String, dynamic>{ | ||
'id': viewId, | ||
'viewType': linkViewType, | ||
}); | ||
if (_isDisposed) { | ||
return; | ||
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. 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 commentThe 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 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.
@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 In fact, if (Note: this LinkViewController was originally modeled to look like 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. 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 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'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 commentThe reason will be displayed to describe this comment to others. Learn more. The framework seems to be disposing of the link while the Something like:
|
||
} | ||
params.onPlatformViewCreated(viewId); | ||
}); | ||
return controller; | ||
} catch (exception, stack) { | ||
FlutterError.reportError(FlutterErrorDetails( | ||
exception: exception, | ||
stack: stack, | ||
library: 'url_launcher', | ||
context: ErrorDescription('while initializing view $viewId'), | ||
)); | ||
} | ||
} | ||
|
||
static final Map<int, LinkViewController> _instances = | ||
|
@@ -159,33 +173,9 @@ class LinkViewController extends PlatformViewController { | |
@override | ||
final int viewId; | ||
|
||
/// The context of the [Link] widget that created this controller. | ||
final BuildContext context; | ||
|
||
late html.Element _element; | ||
|
||
bool get _isInitialized => _element != null; | ||
html.Element _element; | ||
|
||
Future<void> _initialize() async { | ||
_element = html.Element.tag('a'); | ||
setProperty(_element, linkViewIdProperty, viewId); | ||
_element.style | ||
..opacity = '0' | ||
..display = 'block' | ||
..width = '100%' | ||
..height = '100%' | ||
..cursor = 'unset'; | ||
|
||
// This is recommended on MDN: | ||
// - https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-target | ||
_element.setAttribute('rel', 'noreferrer noopener'); | ||
|
||
final Map<String, dynamic> args = <String, dynamic>{ | ||
'id': viewId, | ||
'viewType': linkViewType, | ||
}; | ||
await SystemChannels.platform_views.invokeMethod<void>('create', args); | ||
} | ||
bool _isDisposed = false; | ||
|
||
void _onDomClick(html.MouseEvent event) { | ||
final bool isHitTested = _hitTestedViewId == viewId; | ||
|
@@ -208,7 +198,7 @@ class LinkViewController extends PlatformViewController { | |
// browser handle it. | ||
event.preventDefault(); | ||
final String routeName = _uri.toString(); | ||
pushRouteNameToFramework(context, routeName); | ||
pushRouteNameToFramework(null, routeName); | ||
ditman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
Uri? _uri; | ||
|
@@ -217,7 +207,6 @@ class LinkViewController extends PlatformViewController { | |
/// | ||
/// When Uri is null, the `href` attribute of the link is removed. | ||
void setUri(Uri? uri) { | ||
assert(_isInitialized); | ||
_uri = uri; | ||
if (uri == null) { | ||
_element.removeAttribute('href'); | ||
|
@@ -234,7 +223,6 @@ class LinkViewController extends PlatformViewController { | |
|
||
/// Set the [LinkTarget] value for this link. | ||
void setTarget(LinkTarget target) { | ||
assert(_isInitialized); | ||
_element.setAttribute('target', _getHtmlTarget(target)); | ||
} | ||
|
||
|
@@ -263,18 +251,49 @@ class LinkViewController extends PlatformViewController { | |
} | ||
|
||
@override | ||
Future<void> dispose() async { | ||
if (_isInitialized) { | ||
Future<void> dispose() { | ||
if (!_isDisposed) { | ||
assert(_instances[viewId] == this); | ||
_instances.remove(viewId); | ||
_isDisposed = true; | ||
return _asyncDispose(); | ||
} | ||
return Future<void>.value(); | ||
} | ||
|
||
Future<void> _asyncDispose() async { | ||
try { | ||
if (_instances.isEmpty) { | ||
await _clickSubscription.cancel(); | ||
} | ||
await SystemChannels.platform_views.invokeMethod<void>('dispose', viewId); | ||
} catch (exception, stack) { | ||
FlutterError.reportError(FlutterErrorDetails( | ||
exception: exception, | ||
stack: stack, | ||
library: 'url_launcher', | ||
context: ErrorDescription('while disposing view $viewId'), | ||
)); | ||
} | ||
} | ||
} | ||
|
||
html.Element _makeElement(int viewId) { | ||
final html.Element element = html.Element.tag('a'); | ||
setProperty(element, linkViewIdProperty, viewId); | ||
element.style | ||
..opacity = '0' | ||
..display = 'block' | ||
..width = '100%' | ||
..height = '100%' | ||
..cursor = 'unset'; | ||
|
||
// This is recommended on MDN: | ||
// - https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-target | ||
element.setAttribute('rel', 'noreferrer noopener'); | ||
return element; | ||
} | ||
|
||
/// Finds the view id of the DOM element targeted by the [event]. | ||
int? getViewIdFromTarget(html.Event event) { | ||
final html.Element? linkElement = getLinkElementFromTarget(event); | ||
|
Uh oh!
There was an error while loading. Please reload this page.