Skip to content

Commit a95d8c5

Browse files
author
Hristo Hristov
authored
Fix android activity being destroyed then recreated. (NativeScript#2321)
1 parent c9730c7 commit a95d8c5

File tree

6 files changed

+76
-56
lines changed

6 files changed

+76
-56
lines changed
Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,46 @@
1-
import {activityCallbacks as callbacks} from "ui/frame";
1+
import {setActivityCallbacks, AndroidActivityCallbacks} from "ui/frame";
22

33
@JavaProxy("com.tns.NativeScriptActivity")
44
class NativeScriptActivity extends android.app.Activity {
5+
private _callbacks: AndroidActivityCallbacks;
56
constructor() {
6-
super();
7-
return global.__native(this);
8-
}
7+
super();
8+
return global.__native(this);
9+
}
910

1011
protected onCreate(savedInstanceState: android.os.Bundle): void {
11-
callbacks.onCreate(this, savedInstanceState, super.onCreate);
12+
if (!this._callbacks) {
13+
setActivityCallbacks(this);
14+
}
15+
16+
this._callbacks.onCreate(this, savedInstanceState, super.onCreate);
1217
}
1318

1419
protected onSaveInstanceState(outState: android.os.Bundle): void {
15-
callbacks.onSaveInstanceState(this, outState, super.onSaveInstanceState);
20+
this._callbacks.onSaveInstanceState(this, outState, super.onSaveInstanceState);
1621
}
1722

1823
protected onStart(): void {
19-
callbacks.onStart(this, super.onStart);
24+
this._callbacks.onStart(this, super.onStart);
2025
}
2126

2227
protected onStop(): void {
23-
callbacks.onStop(this, super.onStop);
28+
this._callbacks.onStop(this, super.onStop);
2429
}
2530

2631
protected onDestroy(): void {
27-
callbacks.onDestroy(this, super.onDestroy);
32+
this._callbacks.onDestroy(this, super.onDestroy);
2833
}
2934

3035
public onBackPressed(): void {
31-
callbacks.onBackPressed(this, super.onBackPressed);
36+
this._callbacks.onBackPressed(this, super.onBackPressed);
3237
}
3338

34-
public onRequestPermissionsResult (requestCode: number, permissions: Array<String>, grantResults: Array<number>): void {
35-
callbacks.onRequestPermissionsResult(this, requestCode, permissions, grantResults, undefined /*TODO: Enable if needed*/);
39+
public onRequestPermissionsResult(requestCode: number, permissions: Array<String>, grantResults: Array<number>): void {
40+
this._callbacks.onRequestPermissionsResult(this, requestCode, permissions, grantResults, undefined /*TODO: Enable if needed*/);
3641
}
3742

3843
protected onActivityResult(requestCode: number, resultCode: number, data: android.content.Intent): void {
39-
callbacks.onActivityResult(this, requestCode, resultCode, data, super.onActivityResult);
44+
this._callbacks.onActivityResult(this, requestCode, resultCode, data, super.onActivityResult);
4045
}
4146
}

tns-core-modules/ui/frame/fragment.android.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import * as frame from "ui/frame";
1+
import {AndroidFragmentCallbacks, setFragmentCallbacks, setFragmentClass} from "ui/frame";
22

33
@JavaProxy("com.tns.FragmentClass")
44
class FragmentClass extends android.app.Fragment {
55
// This field is updated in the frame module upon `new` (although hacky this eases the Fragment->callbacks association a lot)
6-
private _callbacks;
6+
private _callbacks: AndroidFragmentCallbacks;
77

88
constructor() {
99
super();
@@ -20,7 +20,11 @@ class FragmentClass extends android.app.Fragment {
2020
}
2121

2222
public onCreate(savedInstanceState: android.os.Bundle) {
23-
super.setHasOptionsMenu(true);
23+
if (!this._callbacks) {
24+
setFragmentCallbacks(this);
25+
}
26+
27+
this.setHasOptionsMenu(true);
2428
this._callbacks.onCreate(this, savedInstanceState, super.onCreate);
2529
}
2630

@@ -46,4 +50,4 @@ class FragmentClass extends android.app.Fragment {
4650
}
4751
}
4852

49-
frame.setFragmentClass(FragmentClass);
53+
setFragmentClass(FragmentClass);

tns-core-modules/ui/frame/frame.android.ts

Lines changed: 41 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ let FRAMEID = "_frameId";
1616
let navDepth = -1;
1717
let fragmentId = -1;
1818
let activityInitialized = false;
19-
const PAGE_FRAGMENT_TAG = "_fragmentTag";
2019
const CALLBACKS = "_callbacks";
2120

2221
function onFragmentShown(fragment: android.app.Fragment) {
2322
if (trace.enabled) {
2423
trace.write(`SHOWN ${fragment}`, trace.categories.NativeLifecycle);
2524
}
25+
2626
let callbacks: FragmentCallbacksImplementation = fragment[CALLBACKS];
2727
if (callbacks.clearHistory) {
2828
// This is the fragment which was at the bottom of the stack (fragment0) when we cleared history and called
@@ -38,7 +38,7 @@ function onFragmentShown(fragment: android.app.Fragment) {
3838
let frame = callbacks.frame;
3939
let entry = callbacks.entry;
4040
let page = entry.resolvedPage;
41-
page[PAGE_FRAGMENT_TAG] = entry.fragmentTag;
41+
page._fragmentTag = entry.fragmentTag;
4242

4343
let currentNavigationContext;
4444
let navigationQueue = (<any>frame)._navigationQueue;
@@ -72,7 +72,7 @@ function onFragmentHidden(fragment: android.app.Fragment, destroyed: boolean) {
7272
let callbacks: FragmentCallbacksImplementation = fragment[CALLBACKS];
7373
let isBack = callbacks.entry.isBack;
7474
callbacks.entry.isBack = undefined;
75-
callbacks.entry.resolvedPage[PAGE_FRAGMENT_TAG] = undefined;
75+
callbacks.entry.resolvedPage._fragmentTag = undefined;
7676

7777
// Handle page transitions.
7878
transitionModule._onFragmentHidden(fragment, isBack, destroyed);
@@ -145,7 +145,7 @@ export class Frame extends frameCommon.Frame {
145145
}
146146

147147
let clearHistory = backstackEntry.entry.clearHistory;
148-
148+
149149
// New Fragment
150150
if (clearHistory) {
151151
navDepth = -1;
@@ -155,15 +155,14 @@ export class Frame extends frameCommon.Frame {
155155
let newFragmentTag = `fragment${fragmentId}[${navDepth}]`;
156156
ensureFragmentClass();
157157
let newFragment: android.app.Fragment = new fragmentClass();
158-
159-
let callbacks = new FragmentCallbacksImplementation();
160-
callbacks.frame = this;
161-
callbacks.entry = backstackEntry;
162-
163-
newFragment[CALLBACKS] = callbacks;
164158
let args = new android.os.Bundle();
165159
args.putInt(FRAMEID, this._android.frameId);
166160
newFragment.setArguments(args);
161+
setFragmentCallbacks(newFragment);
162+
163+
let callbacks = newFragment[CALLBACKS];
164+
callbacks.frame = this;
165+
callbacks.entry = backstackEntry;
167166

168167
// backstackEntry
169168
backstackEntry.isNavigation = true;
@@ -189,7 +188,7 @@ export class Frame extends frameCommon.Frame {
189188

190189
// Clear History
191190
let length = manager.getBackStackEntryCount();
192-
let emptyNativeBackStack = clearHistory && length > 0;
191+
let emptyNativeBackStack = clearHistory && length > 0;
193192
if (emptyNativeBackStack) {
194193
for (let i = 0; i < length; i++) {
195194
let fragmentToRemove = manager.findFragmentByTag(manager.getBackStackEntryAt(i).getName());
@@ -359,7 +358,7 @@ export class Frame extends frameCommon.Frame {
359358

360359
return true;
361360
}
362-
361+
363362
protected _processNavigationContext(navigationContext: frameCommon.NavigationContext) {
364363
let activity = this._android.activity;
365364
if (activity) {
@@ -419,7 +418,7 @@ class AndroidFrame extends Observable implements definition.AndroidFrame {
419418
private _showActionBar = true;
420419
private _owner: Frame;
421420
private _cachePagesOnNavigate: boolean;
422-
421+
423422
constructor(owner: Frame) {
424423
super();
425424
this._owner = owner;
@@ -491,7 +490,7 @@ class AndroidFrame extends Observable implements definition.AndroidFrame {
491490
return activity;
492491
}
493492
}
494-
493+
495494
return undefined;
496495
}
497496

@@ -524,30 +523,31 @@ class AndroidFrame extends Observable implements definition.AndroidFrame {
524523
// can go back only if it is not the main one.
525524
return this.activity.getIntent().getAction() !== android.content.Intent.ACTION_MAIN;
526525
}
527-
526+
528527
public fragmentForPage(page: pages.Page): any {
529-
if(!page) {
528+
if (!page) {
530529
return undefined;
531530
}
532531

533-
let tag = page[PAGE_FRAGMENT_TAG];
534-
if(tag) {
532+
let tag = page._fragmentTag;
533+
if (tag) {
535534
let manager = this.activity.getFragmentManager();
536535
return manager.findFragmentByTag(tag);
537536
}
538-
537+
539538
return undefined;
540539
}
541540
}
542541

543542
function findPageForFragment(fragment: android.app.Fragment, frame: Frame) {
544-
var fragmentTag = fragment.getTag();
545-
var page: pages.Page;
546-
var entry: definition.BackstackEntry;
543+
let fragmentTag = fragment.getTag();
544+
let page: pages.Page;
545+
let entry: definition.BackstackEntry;
547546

548547
if (trace.enabled) {
549548
trace.write(`Finding page for ${fragmentTag}.`, trace.categories.NativeLifecycle);
550549
}
550+
551551
if (fragmentTag === (<any>pages).DIALOG_FRAGMENT_TAG) {
552552
if (trace.enabled) {
553553
trace.write(`No need to find page for dialog fragment.`, trace.categories.NativeLifecycle);
@@ -564,7 +564,7 @@ function findPageForFragment(fragment: android.app.Fragment, frame: Frame) {
564564
}
565565
else {
566566
var backStack = frame.backStack;
567-
for (var i = 0; i < backStack.length; i++) {
567+
for (let i = 0; i < backStack.length; i++) {
568568
if (backStack[i].fragmentTag === fragmentTag) {
569569
entry = backStack[i];
570570
break;
@@ -584,7 +584,7 @@ function findPageForFragment(fragment: android.app.Fragment, frame: Frame) {
584584
callbacks.entry = entry;
585585
}
586586
else {
587-
//throw new Error(`Could not find a page for ${fragmentTag}.`);
587+
throw new Error(`Could not find a page for ${fragmentTag}.`);
588588
}
589589
}
590590

@@ -618,26 +618,27 @@ function ensureAnimationFixed() {
618618
}
619619

620620
function ensureFragmentClass() {
621-
if(fragmentClass) {
621+
if (fragmentClass) {
622622
return;
623623
}
624624

625625
// this require will apply the FragmentClass implementation
626626
require("ui/frame/fragment");
627627

628-
if(!fragmentClass) {
628+
if (!fragmentClass) {
629629
throw new Error("Failed to initialize the extended android.app.Fragment class");
630630
}
631631
}
632632

633633
let fragmentClass: any;
634634
export function setFragmentClass(clazz: any) {
635-
if(fragmentClass) {
635+
if (fragmentClass) {
636636
throw new Error("Fragment class already initialized");
637637
}
638638

639639
fragmentClass = clazz;
640640
}
641+
641642
class FragmentCallbacksImplementation implements definition.AndroidFragmentCallbacks {
642643
public frame: Frame;
643644
public entry: definition.BackstackEntry;
@@ -802,7 +803,7 @@ class ActivityCallbacksImplementation implements definition.AndroidActivityCallb
802803

803804
rootView = frame;
804805
}
805-
806+
806807
// If there is savedInstanceState this call will recreate all fragments that were previously in the navigation.
807808
// We take care of associating them with a Page from our backstack in the onAttachFragment callback.
808809
// If there is savedInstanceState and activityInitialized is false we are restarted but process was killed.
@@ -823,15 +824,15 @@ class ActivityCallbacksImplementation implements definition.AndroidActivityCallb
823824

824825
activityInitialized = true;
825826
}
826-
827+
827828
public onSaveInstanceState(activity: android.app.Activity, outState: android.os.Bundle, superFunc: Function): void {
828829
superFunc.call(activity, outState);
829830
let view = this._rootView;
830831
if (view instanceof Frame) {
831832
outState.putInt(INTENT_EXTRA, view.android.frameId);
832833
}
833834
}
834-
835+
835836
public onStart(activity: any, superFunc: Function): void {
836837
superFunc.call(activity);
837838

@@ -863,12 +864,12 @@ class ActivityCallbacksImplementation implements definition.AndroidActivityCallb
863864
}
864865

865866
superFunc.call(activity);
866-
867+
867868
if (trace.enabled) {
868869
trace.write("NativeScriptActivity.onDestroy();", trace.categories.NativeLifecycle);
869870
}
870871
}
871-
872+
872873
public onBackPressed(activity: any, superFunc: Function): void {
873874
if (trace.enabled) {
874875
trace.write("NativeScriptActivity.onBackPressed;", trace.categories.NativeLifecycle);
@@ -890,7 +891,7 @@ class ActivityCallbacksImplementation implements definition.AndroidActivityCallb
890891
superFunc.call(activity);
891892
}
892893
}
893-
894+
894895
public onRequestPermissionsResult(activity: any, requestCode: number, permissions: Array<String>, grantResults: Array<number>, superFunc: Function): void {
895896
if (trace.enabled) {
896897
trace.write("NativeScriptActivity.onRequestPermissionsResult;", trace.categories.NativeLifecycle);
@@ -928,4 +929,10 @@ class ActivityCallbacksImplementation implements definition.AndroidActivityCallb
928929
}
929930
}
930931

931-
export var activityCallbacks = new ActivityCallbacksImplementation();
932+
export function setActivityCallbacks(activity: android.app.Activity): void {
933+
activity[CALLBACKS] = new ActivityCallbacksImplementation();
934+
}
935+
936+
export function setFragmentCallbacks(fragment: android.app.Fragment): void {
937+
fragment[CALLBACKS] = new FragmentCallbacksImplementation();
938+
}

tns-core-modules/ui/frame/frame.d.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,6 @@ declare module "ui/frame" {
122122
on(event: "optionSelected", callback: (args: observable.EventData) => void, thisArg?: any);
123123
}
124124

125-
/**
126-
* Gets the default AndroidActivityCallbacks implementation, used to bridge Activity events to the Frame and navigation routine. This field is initialized only for the Android platform.
127-
*/
128-
export var activityCallbacks: AndroidActivityCallbacks;
129-
130125
/**
131126
* Sets the extended android.app.Fragment class to the Frame and navigation routine. An instance of this class will be created to represent the Page currently visible on the srceen. This method is available only for the Android platform.
132127
*/
@@ -356,5 +351,7 @@ declare module "ui/frame" {
356351
//@private
357352
function reloadPage(): void;
358353
function resolvePageFromEntry(entry: NavigationEntry): pages.Page;
354+
function setFragmentCallbacks(fragment: android.app.Fragment): void;
355+
function setActivityCallbacks(activity: android.app.Activity): void;
359356
//@endprivate
360357
}

tns-core-modules/ui/page/page-common.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export class Page extends ContentView implements dts.Page {
6060
private _actionBar: ActionBar;
6161

6262
public _modal: Page;
63+
public _fragmentTag: string;
6364

6465
constructor() {
6566
super();

tns-core-modules/ui/page/page.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,12 @@ declare module "ui/page" {
217217

218218
//@private
219219

220+
/**
221+
* identifier for the fragment that shows this page.
222+
* Android only.
223+
*/
224+
_fragmentTag: string;
225+
220226
/**
221227
* A method called before navigating to the page.
222228
* @param context - The data passed to the page through the NavigationEntry.context property.

0 commit comments

Comments
 (0)