feat: activityBackPressed handling with OnBackPressedCallback

This commit is contained in:
Osei Fortune
2025-09-22 00:03:51 -04:00
parent f3ed1d8f7a
commit 0e87b0e9c9
3 changed files with 115 additions and 4 deletions

View File

@@ -24,7 +24,8 @@ import * as Utils from '../../../utils';
import { SDK_VERSION } from '../../../utils/constants';
import { BoxShadow } from '../../styling/box-shadow';
import { NativeScriptAndroidView } from '../../utils';
import { Device } from '../../../platform';
import { Frame } from '../../frame';
export * from './view-common';
// helpers (these are okay re-exported here)
export * from './view-helper';
@@ -52,6 +53,42 @@ const GRAVITY_FILL_VERTICAL = 112; // android.view.Gravity.FILL_VERTICAL
const modalMap = new Map<number, DialogOptions>();
let OnBackPressedCallback;
if (parseInt(Device.sdkVersion) >= 33) {
OnBackPressedCallback = (<any>androidx.activity.OnBackPressedCallback).extend({
handleOnBackPressed() {
const dialog = this['_dialog']?.get();
if (!dialog) {
// disable the callback and call super to avoid infinite loop
this.setEnabled(false);
return;
}
const view = dialog.fragment.owner;
const args = <AndroidActivityBackPressedEventData>{
eventName: 'activityBackPressed',
object: view,
activity: view._context,
cancel: false,
};
// Fist fire application.android global event
Application.android.notify(args);
if (args.cancel) {
return;
}
view.notify(args);
if (!args.cancel) {
this.setEnabled(false);
dialog.getOnBackPressedDispatcher().onBackPressed();
this.setEnabled(true);
}
},
});
}
let TouchListener: TouchListener;
let DialogFragment: DialogFragment;
@@ -115,13 +152,21 @@ function initializeDialogFragment() {
}
@NativeClass
class DialogImpl extends android.app.Dialog {
class DialogImpl extends androidx.appcompat.app.AppCompatDialog {
constructor(
public fragment: DialogFragmentImpl,
context: android.content.Context,
themeResId: number,
) {
super(context, themeResId);
// @ts-ignore
if (parseInt(Device.sdkVersion) >= 33 && OnBackPressedCallback) {
const callback = new OnBackPressedCallback(true);
callback['_dialog'] = new WeakRef(this);
// @ts-ignore
this.getOnBackPressedDispatcher().addCallback(this, callback);
}
return global.__native(this);
}
@@ -132,6 +177,10 @@ function initializeDialogFragment() {
}
public onBackPressed(): void {
if (parseInt(Device.sdkVersion) >= 33) {
super.onBackPressed();
return;
}
const view = this.fragment.owner;
const args = <AndroidActivityBackPressedEventData>{
eventName: 'activityBackPressed',

View File

@@ -4,7 +4,7 @@ import { Page } from '../page';
import { TransitionState } from './frame-common';
// Types.
import { Application } from '../../application';
import { Application, AndroidActivityBackPressedEventData } from '../../application';
import { Observable } from '../../data/observable';
import { Trace } from '../../trace';
@@ -19,6 +19,7 @@ import type { ExpandedEntry } from './fragment.transitions.android';
import { ensureFragmentClass, fragmentClass } from './fragment';
import { FragmentCallbacksImplementation } from './callbacks/fragment-callbacks';
import { ActivityCallbacksImplementation } from './callbacks/activity-callbacks';
import { Device } from '../../platform';
export * from './frame-common';
export { setFragmentClass } from './fragment';
@@ -782,8 +783,69 @@ export function getFrameByNumberId(frameId: number): Frame {
return null;
}
let OnBackPressedCallback;
if (parseInt(Device.sdkVersion) >= 33) {
OnBackPressedCallback = (<any>androidx.activity.OnBackPressedCallback).extend('com.tns.OnBackPressedCallback', {
handleOnBackPressed() {
if (Trace.isEnabled()) {
Trace.write('NativeScriptActivity.onBackPressed;', Trace.categories.NativeLifecycle);
}
const activity = this['_activity']?.get();
if (!activity) {
if (Trace.isEnabled()) {
Trace.write('NativeScriptActivity.onBackPressed; Activity is null, calling super', Trace.categories.NativeLifecycle);
}
this.setEnabled(false);
return;
}
const args = <AndroidActivityBackPressedEventData>{
eventName: 'activityBackPressed',
object: Application,
android: Application.android,
activity: activity,
cancel: false,
};
Application.android.notify(args);
if (args.cancel) {
return;
}
const view = activity._rootView;
let callSuper = false;
const viewArgs = <AndroidActivityBackPressedEventData>{
eventName: 'activityBackPressed',
object: view,
activity: activity,
cancel: false,
};
view?.notify(viewArgs);
// In the case of Frame, use this callback only if it was overridden, since the original will cause navigation issues
if (!viewArgs.cancel && (view?.onBackPressed === Frame.prototype.onBackPressed || !view?.onBackPressed())) {
callSuper = view instanceof Frame ? !Frame.goBack() : true;
}
if (callSuper) {
this.setEnabled(false);
activity.getOnBackPressedDispatcher().onBackPressed();
this.setEnabled(true);
}
},
});
}
export function setActivityCallbacks(activity: androidx.appcompat.app.AppCompatActivity): void {
activity[CALLBACKS] = new ActivityCallbacksImplementation();
if (OnBackPressedCallback && !activity['_onBackPressed']) {
const callback = new OnBackPressedCallback(true);
callback['_activity'] = new WeakRef(activity);
activity['_onBackPressed'] = true;
(activity as androidx.activity.ComponentActivity).getOnBackPressedDispatcher().addCallback(activity, callback);
}
}
export function setFragmentCallbacks(fragment: androidx.fragment.app.Fragment): void {

View File

@@ -205,7 +205,7 @@ declare module androidx {
declare module androidx {
export module activity {
export class ComponentDialog implements androidx.activity.OnBackPressedDispatcherOwner {
export class ComponentDialog extends android.app.Dialog implements androidx.activity.OnBackPressedDispatcherOwner {
public static class: java.lang.Class<androidx.activity.ComponentDialog>;
public onStart(): void;
public constructor(context: globalAndroid.content.Context);