fix(core): improve loaded/unloaded handling to be stable and consistent (#10170)

This commit is contained in:
Nathan Walker
2023-01-15 19:49:28 -08:00
committed by GitHub
parent a69a9d6921
commit c9e29aa9af
12 changed files with 121 additions and 166 deletions

View File

@ -291,22 +291,24 @@ export class Observable implements ObservableDefinition {
} }
for (let i = observers.length - 1; i >= 0; i--) { for (let i = observers.length - 1; i >= 0; i--) {
const entry = observers[i]; const entry = observers[i];
if (entry.once) { if (entry) {
observers.splice(i, 1); if (entry.once) {
} observers.splice(i, 1);
}
let returnValue; let returnValue;
if (entry.thisArg) { if (entry.thisArg) {
returnValue = entry.callback.apply(entry.thisArg, [data]); returnValue = entry.callback.apply(entry.thisArg, [data]);
} else { } else {
returnValue = entry.callback(data); returnValue = entry.callback(data);
} }
// This ensures errors thrown inside asynchronous functions do not get swallowed // This ensures errors thrown inside asynchronous functions do not get swallowed
if (returnValue && returnValue instanceof Promise) { if (returnValue && returnValue instanceof Promise) {
returnValue.catch((err) => { returnValue.catch((err) => {
console.error(err); console.error(err);
}); });
}
} }
} }
} }

View File

@ -21,9 +21,8 @@ export class Button extends ButtonBase {
public initNativeView(): void { public initNativeView(): void {
super.initNativeView(); super.initNativeView();
const nativeView = this.nativeViewProtected;
this._tapHandler = TapHandlerImpl.initWithOwner(new WeakRef(this)); this._tapHandler = TapHandlerImpl.initWithOwner(new WeakRef(this));
nativeView.addTargetActionForControlEvents(this._tapHandler, 'tap', UIControlEvents.TouchUpInside); this.nativeViewProtected.addTargetActionForControlEvents(this._tapHandler, 'tap', UIControlEvents.TouchUpInside);
} }
public disposeNativeView(): void { public disposeNativeView(): void {

View File

@ -430,14 +430,6 @@ export class View extends ViewCommon {
@profile @profile
public onUnloaded() { public onUnloaded() {
if (this.touchListenerIsSet) {
this.touchListenerIsSet = false;
if (this.nativeViewProtected) {
this.nativeViewProtected.setOnTouchListener(null);
this.nativeViewProtected.setClickable(this._isClickable);
}
}
this._manager = null; this._manager = null;
this._rootManager = null; this._rootManager = null;
super.onUnloaded(); super.onUnloaded();
@ -474,7 +466,6 @@ export class View extends ViewCommon {
public initNativeView(): void { public initNativeView(): void {
super.initNativeView(); super.initNativeView();
this._isClickable = this.nativeViewProtected.isClickable(); this._isClickable = this.nativeViewProtected.isClickable();
if (this.needsOnLayoutChangeListener()) { if (this.needsOnLayoutChangeListener()) {
this.setOnLayoutChangeListener(); this.setOnLayoutChangeListener();
} }
@ -486,7 +477,12 @@ export class View extends ViewCommon {
public disposeNativeView(): void { public disposeNativeView(): void {
super.disposeNativeView(); super.disposeNativeView();
if (this.touchListenerIsSet) {
this.touchListenerIsSet = false;
if (this.nativeViewProtected) {
this.nativeViewProtected.setOnTouchListener(null);
}
}
if (this.layoutChangeListenerIsSet) { if (this.layoutChangeListenerIsSet) {
this.layoutChangeListenerIsSet = false; this.layoutChangeListenerIsSet = false;
this.nativeViewProtected.removeOnLayoutChangeListener(this.layoutChangeListener); this.nativeViewProtected.removeOnLayoutChangeListener(this.layoutChangeListener);
@ -494,7 +490,7 @@ export class View extends ViewCommon {
} }
setOnTouchListener() { setOnTouchListener() {
if (!this.nativeViewProtected || !this.hasGestureObservers()) { if (this.touchListenerIsSet || !this.nativeViewProtected || !this.hasGestureObservers()) {
return; return;
} }

View File

@ -479,12 +479,14 @@ export class View extends ViewCommon implements ViewDefinition {
} else { } else {
//use CSS & attribute width & height if option is not provided //use CSS & attribute width & height if option is not provided
const handler = () => { const handler = () => {
const w = <number>(this.width || this.style.width); if (controller) {
const h = <number>(this.height || this.style.height); const w = <number>(this.width || this.style.width);
const h = <number>(this.height || this.style.height);
//TODO: only numeric value is supported, percentage value is not supported like Android //TODO: only numeric value is supported, percentage value is not supported like Android
if (w > 0 && h > 0) { if (w > 0 && h > 0) {
controller.preferredContentSize = CGSizeMake(w, h); controller.preferredContentSize = CGSizeMake(w, h);
}
} }
this.off(View.loadedEvent, handler); this.off(View.loadedEvent, handler);

View File

@ -17,9 +17,9 @@ export class ListPicker extends ListPickerBase {
initNativeView() { initNativeView() {
super.initNativeView(); super.initNativeView();
const nativeView = this.nativeViewProtected; this.nativeViewProtected.dataSource = this._dataSource = ListPickerDataSource.initWithOwner(new WeakRef(this));
nativeView.dataSource = this._dataSource = ListPickerDataSource.initWithOwner(new WeakRef(this));
this._delegate = ListPickerDelegateImpl.initWithOwner(new WeakRef(this)); this._delegate = ListPickerDelegateImpl.initWithOwner(new WeakRef(this));
this.nativeViewProtected.delegate = this._delegate;
} }
public disposeNativeView() { public disposeNativeView() {
@ -33,17 +33,6 @@ export class ListPicker extends ListPickerBase {
return this.nativeViewProtected; return this.nativeViewProtected;
} }
@profile
public onLoaded() {
super.onLoaded();
this.ios.delegate = this._delegate;
}
public onUnloaded() {
this.ios.delegate = null;
super.onUnloaded();
}
[selectedIndexProperty.getDefault](): number { [selectedIndexProperty.getDefault](): number {
return -1; return -1;
} }

View File

@ -118,17 +118,10 @@ export class ScrollView extends ScrollViewBase {
this.nativeViewProtected.setId(this._androidViewId); this.nativeViewProtected.setId(this._androidViewId);
} }
public _onOrientationChanged() { protected addNativeListener() {
if (this.nativeViewProtected) { if (!this.nativeViewProtected) {
const parent = this.parent; return;
if (parent) {
parent._removeView(this);
parent._addView(this);
}
} }
}
protected attachNative() {
const that = new WeakRef(this); const that = new WeakRef(this);
if (this.orientation === 'vertical') { if (this.orientation === 'vertical') {
this.scrollChangeHandler = new androidx.core.widget.NestedScrollView.OnScrollChangeListener({ this.scrollChangeHandler = new androidx.core.widget.NestedScrollView.OnScrollChangeListener({
@ -144,7 +137,7 @@ export class ScrollView extends ScrollViewBase {
} }
}, },
}); });
this.nativeView.setOnScrollChangeListener(this.scrollChangeHandler); this.nativeViewProtected.setOnScrollChangeListener(this.scrollChangeHandler);
} else { } else {
this.handler = new android.view.ViewTreeObserver.OnScrollChangedListener({ this.handler = new android.view.ViewTreeObserver.OnScrollChangedListener({
onScrollChanged: function () { onScrollChanged: function () {
@ -158,6 +151,35 @@ export class ScrollView extends ScrollViewBase {
} }
} }
protected removeNativeListener() {
if (!this.nativeViewProtected) {
return;
}
if (this.handler) {
this.nativeViewProtected?.getViewTreeObserver().removeOnScrollChangedListener(this.handler);
this.handler = null;
}
if (this.scrollChangeHandler) {
this.nativeView?.setOnScrollChangeListener(null);
this.scrollChangeHandler = null;
}
}
disposeNativeView() {
super.disposeNativeView();
this.removeNativeListener();
}
public _onOrientationChanged() {
if (this.nativeViewProtected) {
const parent = this.parent;
if (parent) {
parent._removeView(this);
parent._addView(this);
}
}
}
private _lastScrollX = -1; private _lastScrollX = -1;
private _lastScrollY = -1; private _lastScrollY = -1;
private _onScrollChanged() { private _onScrollChanged() {
@ -179,17 +201,6 @@ export class ScrollView extends ScrollViewBase {
} }
} }
} }
protected dettachNative() {
if (this.handler) {
this.nativeViewProtected?.getViewTreeObserver().removeOnScrollChangedListener(this.handler);
this.handler = null;
}
if (this.scrollChangeHandler) {
this.nativeView?.setOnScrollChangeListener(null);
this.scrollChangeHandler = null;
}
}
} }
ScrollView.prototype.recycleNativeView = 'never'; ScrollView.prototype.recycleNativeView = 'never';

View File

@ -40,9 +40,7 @@ export class ScrollView extends ScrollViewBase {
private _delegate: UIScrollViewDelegateImpl; private _delegate: UIScrollViewDelegateImpl;
public createNativeView() { public createNativeView() {
const view = UIScrollView.new(); return UIScrollView.new();
return view;
} }
initNativeView() { initNativeView() {
@ -51,20 +49,34 @@ export class ScrollView extends ScrollViewBase {
this._setNativeClipToBounds(); this._setNativeClipToBounds();
} }
_setNativeClipToBounds() { disposeNativeView() {
// Always set clipsToBounds for scroll-view this._delegate = null;
this.nativeViewProtected.clipsToBounds = true; super.disposeNativeView();
} }
protected attachNative() { protected addNativeListener() {
if (!this.nativeViewProtected) {
return;
}
this._delegate = UIScrollViewDelegateImpl.initWithOwner(new WeakRef(this)); this._delegate = UIScrollViewDelegateImpl.initWithOwner(new WeakRef(this));
this.nativeViewProtected.delegate = this._delegate; this.nativeViewProtected.delegate = this._delegate;
} }
protected dettachNative() { protected removeNativeListener() {
if (!this.nativeViewProtected) {
return;
}
this.nativeViewProtected.delegate = null; this.nativeViewProtected.delegate = null;
} }
_setNativeClipToBounds() {
if (!this.nativeViewProtected) {
return;
}
// Always set clipsToBounds for scroll-view
this.nativeViewProtected.clipsToBounds = true;
}
protected updateScrollBarVisibility(value) { protected updateScrollBarVisibility(value) {
if (!this.nativeViewProtected) { if (!this.nativeViewProtected) {
return; return;

View File

@ -9,7 +9,7 @@ import { CoreTypes } from '../../core-types';
@CSSType('ScrollView') @CSSType('ScrollView')
export abstract class ScrollViewBase extends ContentView implements ScrollViewDefinition { export abstract class ScrollViewBase extends ContentView implements ScrollViewDefinition {
private _scrollChangeCount = 0; private _addedScrollEvent = false;
public static scrollEvent = 'scroll'; public static scrollEvent = 'scroll';
public orientation: CoreTypes.OrientationType; public orientation: CoreTypes.OrientationType;
@ -19,52 +19,27 @@ export abstract class ScrollViewBase extends ContentView implements ScrollViewDe
public addEventListener(arg: string, callback: any, thisArg?: any) { public addEventListener(arg: string, callback: any, thisArg?: any) {
super.addEventListener(arg, callback, thisArg); super.addEventListener(arg, callback, thisArg);
if (arg === ScrollViewBase.scrollEvent) { if (arg === ScrollViewBase.scrollEvent && !this._addedScrollEvent) {
this._scrollChangeCount++; this._addedScrollEvent = true;
this.attach(); this.addNativeListener();
} }
} }
public removeEventListener(arg: string, callback: any, thisArg?: any) { public removeEventListener(arg: string, callback: any, thisArg?: any) {
super.removeEventListener(arg, callback, thisArg); super.removeEventListener(arg, callback, thisArg);
if (arg === ScrollViewBase.scrollEvent) { if (arg === ScrollViewBase.scrollEvent && this._addedScrollEvent) {
this._scrollChangeCount--; this._addedScrollEvent = false;
this.dettach(); this.removeNativeListener();
} }
} }
@profile protected addNativeListener() {
public onLoaded() { // implemented per platform
super.onLoaded();
this.attach();
} }
public onUnloaded() { protected removeNativeListener() {
super.onUnloaded(); // implemented per platform
this.dettach();
}
private attach() {
if (this._scrollChangeCount > 0 && this.isLoaded) {
this.attachNative();
}
}
private dettach() {
if (this._scrollChangeCount === 0 && this.isLoaded) {
this.dettachNative();
}
}
protected attachNative() {
//
}
protected dettachNative() {
//
} }
get horizontalOffset(): number { get horizontalOffset(): number {

View File

@ -82,6 +82,7 @@ export class SearchBar extends SearchBarBase {
initNativeView() { initNativeView() {
super.initNativeView(); super.initNativeView();
this._delegate = UISearchBarDelegateImpl.initWithOwner(new WeakRef(this)); this._delegate = UISearchBarDelegateImpl.initWithOwner(new WeakRef(this));
this.nativeViewProtected.delegate = this._delegate;
} }
disposeNativeView() { disposeNativeView() {
@ -89,16 +90,6 @@ export class SearchBar extends SearchBarBase {
super.disposeNativeView(); super.disposeNativeView();
} }
public onLoaded() {
super.onLoaded();
this.ios.delegate = this._delegate;
}
public onUnloaded() {
this.ios.delegate = null;
super.onUnloaded();
}
public dismissSoftInput() { public dismissSoftInput() {
(<UIResponder>this.ios).resignFirstResponder(); (<UIResponder>this.ios).resignFirstResponder();
} }

View File

@ -123,6 +123,7 @@ export class TextField extends TextFieldBase {
initNativeView() { initNativeView() {
super.initNativeView(); super.initNativeView();
this._delegate = UITextFieldDelegateImpl.initWithOwner(new WeakRef(this)); this._delegate = UITextFieldDelegateImpl.initWithOwner(new WeakRef(this));
this.nativeViewProtected.delegate = this._delegate;
} }
disposeNativeView() { disposeNativeView() {
@ -130,17 +131,6 @@ export class TextField extends TextFieldBase {
super.disposeNativeView(); super.disposeNativeView();
} }
@profile
public onLoaded() {
super.onLoaded();
this.ios.delegate = this._delegate;
}
public onUnloaded() {
this.ios.delegate = null;
super.onUnloaded();
}
// @ts-ignore // @ts-ignore
get ios(): UITextField { get ios(): UITextField {
return this.nativeViewProtected; return this.nativeViewProtected;

View File

@ -106,6 +106,7 @@ export class TextView extends TextViewBaseCommon {
initNativeView() { initNativeView() {
super.initNativeView(); super.initNativeView();
this._delegate = UITextViewDelegateImpl.initWithOwner(new WeakRef(this)); this._delegate = UITextViewDelegateImpl.initWithOwner(new WeakRef(this));
this.nativeTextViewProtected.delegate = this._delegate;
} }
disposeNativeView() { disposeNativeView() {
@ -113,17 +114,6 @@ export class TextView extends TextViewBaseCommon {
super.disposeNativeView(); super.disposeNativeView();
} }
@profile
public onLoaded() {
super.onLoaded();
this.nativeTextViewProtected.delegate = this._delegate;
}
public onUnloaded() {
this.nativeTextViewProtected.delegate = null;
super.onUnloaded();
}
// @ts-ignore // @ts-ignore
get ios(): UITextView { get ios(): UITextView {
return this.nativeViewProtected; return this.nativeViewProtected;

View File

@ -202,18 +202,16 @@ export class WebView extends WebViewBase {
this._delegate = WKNavigationDelegateImpl.initWithOwner(new WeakRef(this)); this._delegate = WKNavigationDelegateImpl.initWithOwner(new WeakRef(this));
this._scrollDelegate = UIScrollViewDelegateImpl.initWithOwner(new WeakRef(this)); this._scrollDelegate = UIScrollViewDelegateImpl.initWithOwner(new WeakRef(this));
this._uiDelegate = WKUIDelegateImpl.initWithOwner(new WeakRef(this)); this._uiDelegate = WKUIDelegateImpl.initWithOwner(new WeakRef(this));
this.ios.navigationDelegate = this._delegate; this.nativeViewProtected.navigationDelegate = this._delegate;
this.ios.scrollView.delegate = this._scrollDelegate; this.nativeViewProtected.scrollView.delegate = this._scrollDelegate;
this.ios.UIDelegate = this._uiDelegate; this.nativeViewProtected.UIDelegate = this._uiDelegate;
} }
@profile disposeNativeView() {
public onLoaded() { super.disposeNativeView();
super.onLoaded(); this._delegate = null;
} this._scrollDelegate = null;
this._uiDelegate = null;
public onUnloaded() {
super.onUnloaded();
} }
// @ts-ignore // @ts-ignore
@ -222,48 +220,48 @@ export class WebView extends WebViewBase {
} }
public stopLoading() { public stopLoading() {
this.ios.stopLoading(); this.nativeViewProtected.stopLoading();
} }
public _loadUrl(src: string) { public _loadUrl(src: string) {
if (src.startsWith('file:///')) { if (src.startsWith('file:///')) {
const cachePath = src.substring(0, src.lastIndexOf('/')); const cachePath = src.substring(0, src.lastIndexOf('/'));
this.ios.loadFileURLAllowingReadAccessToURL(NSURL.URLWithString(src), NSURL.URLWithString(cachePath)); this.nativeViewProtected.loadFileURLAllowingReadAccessToURL(NSURL.URLWithString(src), NSURL.URLWithString(cachePath));
} else { } else {
this.ios.loadRequest(NSURLRequest.requestWithURL(NSURL.URLWithString(src))); this.nativeViewProtected.loadRequest(NSURLRequest.requestWithURL(NSURL.URLWithString(src)));
} }
} }
public _loadData(content: string) { public _loadData(content: string) {
this.ios.loadHTMLStringBaseURL(content, NSURL.alloc().initWithString(`file:///${knownFolders.currentApp().path}/`)); this.nativeViewProtected.loadHTMLStringBaseURL(content, NSURL.alloc().initWithString(`file:///${knownFolders.currentApp().path}/`));
} }
get canGoBack(): boolean { get canGoBack(): boolean {
return this.ios.canGoBack; return this.nativeViewProtected.canGoBack;
} }
get canGoForward(): boolean { get canGoForward(): boolean {
return this.ios.canGoForward; return this.nativeViewProtected.canGoForward;
} }
public goBack() { public goBack() {
this.ios.goBack(); this.nativeViewProtected.goBack();
} }
public goForward() { public goForward() {
this.ios.goForward(); this.nativeViewProtected.goForward();
} }
public reload() { public reload() {
this.ios.reload(); this.nativeViewProtected.reload();
} }
[disableZoomProperty.setNative](value: boolean) { [disableZoomProperty.setNative](value: boolean) {
if (!value && typeof this._minimumZoomScale === 'number' && typeof this._maximumZoomScale === 'number' && typeof this._zoomScale === 'number') { if (!value && typeof this._minimumZoomScale === 'number' && typeof this._maximumZoomScale === 'number' && typeof this._zoomScale === 'number') {
if (this.ios.scrollView) { if (this.nativeViewProtected?.scrollView) {
this.ios.scrollView.minimumZoomScale = this._minimumZoomScale; this.nativeViewProtected.scrollView.minimumZoomScale = this._minimumZoomScale;
this.ios.scrollView.maximumZoomScale = this._maximumZoomScale; this.nativeViewProtected.scrollView.maximumZoomScale = this._maximumZoomScale;
this.ios.scrollView.zoomScale = this._zoomScale; this.nativeViewProtected.scrollView.zoomScale = this._zoomScale;
this._minimumZoomScale = undefined; this._minimumZoomScale = undefined;
this._maximumZoomScale = undefined; this._maximumZoomScale = undefined;
this._zoomScale = undefined; this._zoomScale = undefined;