From 501d310431b19e28f75e27b16a404324a960a79a Mon Sep 17 00:00:00 2001 From: farfromrefuge Date: Thu, 24 Nov 2022 04:54:34 +0000 Subject: [PATCH] fix(android): memory leak with EditableTextBase (#10052) --- .../ui/editable-text-base/index.android.ts | 148 ++++++++++-------- 1 file changed, 81 insertions(+), 67 deletions(-) diff --git a/packages/core/ui/editable-text-base/index.android.ts b/packages/core/ui/editable-text-base/index.android.ts index 92e25e220..3e705d71d 100644 --- a/packages/core/ui/editable-text-base/index.android.ts +++ b/packages/core/ui/editable-text-base/index.android.ts @@ -27,12 +27,11 @@ function clearDismissTimer(): void { } } -function dismissSoftInput(_owner: WeakRef): void { +function dismissSoftInput(view: EditableTextBase): void { clearDismissTimer(); if (!dismissKeyboardTimeoutId) { dismissKeyboardTimeoutId = setTimeout(() => { - const owner = _owner && _owner.get(); - const activity = owner?._context as androidx.appcompat.app.AppCompatActivity; + const activity = view._context as androidx.appcompat.app.AppCompatActivity; dismissKeyboardTimeoutId = null; const focused = activity && activity.getCurrentFocus(); if (focused && !(focused instanceof android.widget.EditText)) { @@ -61,81 +60,23 @@ function initializeEditTextListeners(): void { } public beforeTextChanged(text: string, start: number, count: number, after: number): void { - // + this.owner?.get()?.beforeTextChanged(text, start, count, after); } public onTextChanged(text: string, start: number, before: number, count: number): void { - // const owner = this.owner; - // let selectionStart = owner.android.getSelectionStart(); - // owner.android.removeTextChangedListener(owner._editTextListeners); - // owner.android.addTextChangedListener(owner._editTextListeners); - // owner.android.setSelection(selectionStart); + this.owner?.get()?.onTextChanged(text, start, before, count); } public afterTextChanged(editable: android.text.Editable): void { - const owner = this.owner && this.owner.get(); - if (!owner || owner._changeFromCode) { - return; - } - - switch (owner.updateTextTrigger) { - case 'focusLost': - owner._dirtyTextAccumulator = editable.toString(); - break; - case 'textChanged': - textProperty.nativeValueChange(owner, editable.toString()); - break; - default: - throw new Error('Invalid updateTextTrigger: ' + owner.updateTextTrigger); - } + this.owner?.get()?.afterTextChanged(editable); } public onFocusChange(view: android.view.View, hasFocus: boolean): void { - const owner = this.owner && this.owner.get(); - if (!owner) { - return; - } - - if (hasFocus) { - clearDismissTimer(); - owner.notify({ - eventName: EditableTextBase.focusEvent, - object: owner, - }); - } else { - if (owner._dirtyTextAccumulator || owner._dirtyTextAccumulator === '') { - textProperty.nativeValueChange(owner, owner._dirtyTextAccumulator); - owner._dirtyTextAccumulator = undefined; - } - - owner.notify({ - eventName: EditableTextBase.blurEvent, - object: owner, - }); - dismissSoftInput(this.owner); - } + this.owner?.get()?.onFocusChange(view, hasFocus); } public onEditorAction(textView: android.widget.TextView, actionId: number, event: android.view.KeyEvent): boolean { - const owner = this.owner && this.owner.get(); - if (!owner) { - return false; - } - - if (actionId === android.view.inputmethod.EditorInfo.IME_ACTION_DONE || actionId === android.view.inputmethod.EditorInfo.IME_ACTION_UNSPECIFIED || (event && event.getKeyCode() === android.view.KeyEvent.KEYCODE_ENTER)) { - // If it is TextField, close the keyboard. If it is TextView, do not close it since the TextView is multiline - // https://github.com/NativeScript/NativeScript/issues/3111 - if (textView.getMaxLines() === 1) { - owner.dismissSoftInput(); - } - - owner._onReturnPress(); - } else if (actionId === android.view.inputmethod.EditorInfo.IME_ACTION_NEXT || actionId === android.view.inputmethod.EditorInfo.IME_ACTION_PREVIOUS) { - // do not close keyboard for ACTION_NEXT or ACTION_PREVIOUS - owner._onReturnPress(); - } - - return false; + return this.owner?.get()?.onEditorAction(textView, actionId, event) || false; } } @@ -178,7 +119,12 @@ export abstract class EditableTextBase extends EditableTextBaseCommon { } public disposeNativeView(): void { - (this.nativeTextViewProtected).listener.owner = null; + const editText = this.nativeTextViewProtected; + editText.removeTextChangedListener((editText).listener); + editText.setOnFocusChangeListener(null); + editText.setOnEditorActionListener(null); + (editText).listener.owner = null; + (editText).listener = null; this._keyListenerCache = null; super.disposeNativeView(); } @@ -531,4 +477,72 @@ export abstract class EditableTextBase extends EditableTextBaseCommon { } } } + + public beforeTextChanged(text: string, start: number, count: number, after: number): void { + // called by android.text.TextWatcher + } + + public onTextChanged(text: string, start: number, before: number, count: number): void { + // called by android.text.TextWatcher + // const owner = this.owner; + // let selectionStart = owner.android.getSelectionStart(); + // owner.android.removeTextChangedListener(owner._editTextListeners); + // owner.android.addTextChangedListener(owner._editTextListeners); + // owner.android.setSelection(selectionStart); + } + + public afterTextChanged(editable: android.text.Editable): void { + // called by android.text.TextWatcher + if (this._changeFromCode) { + return; + } + + switch (this.updateTextTrigger) { + case 'focusLost': + this._dirtyTextAccumulator = editable.toString(); + break; + case 'textChanged': + textProperty.nativeValueChange(this, editable.toString()); + break; + default: + throw new Error('Invalid updateTextTrigger: ' + this.updateTextTrigger); + } + } + + public onFocusChange(view: android.view.View, hasFocus: boolean): void { + if (hasFocus) { + clearDismissTimer(); + this.notify({ + eventName: EditableTextBase.focusEvent, + object: this, + }); + } else { + if (this._dirtyTextAccumulator || this._dirtyTextAccumulator === '') { + textProperty.nativeValueChange(this, this._dirtyTextAccumulator); + this._dirtyTextAccumulator = undefined; + } + + this.notify({ + eventName: EditableTextBase.blurEvent, + }); + dismissSoftInput(this); + } + } + + public onEditorAction(textView: android.widget.TextView, actionId: number, event: android.view.KeyEvent): boolean { + if (actionId === android.view.inputmethod.EditorInfo.IME_ACTION_DONE || actionId === android.view.inputmethod.EditorInfo.IME_ACTION_UNSPECIFIED || (event && event.getKeyCode() === android.view.KeyEvent.KEYCODE_ENTER)) { + // If it is TextField, close the keyboard. If it is TextView, do not close it since the TextView is multiline + // https://github.com/NativeScript/NativeScript/issues/3111 + if (textView.getMaxLines() === 1) { + this.dismissSoftInput(); + } + + this._onReturnPress(); + } else if (actionId === android.view.inputmethod.EditorInfo.IME_ACTION_NEXT || actionId === android.view.inputmethod.EditorInfo.IME_ACTION_PREVIOUS) { + // do not close keyboard for ACTION_NEXT or ACTION_PREVIOUS + this._onReturnPress(); + } + + return false; + } }