Decouple Fragment implementation logic from the Extend call.

This commit is contained in:
atanasovg
2016-06-10 18:01:47 +03:00
parent 64d9e23d2d
commit ef0577ed56
5 changed files with 155 additions and 68 deletions

View File

@ -0,0 +1,49 @@
import * as frame from "ui/frame";
@JavaProxy("com.tns.FragmentClass")
class FragmentClass extends android.app.Fragment {
// This field is updated in the frame module upon `new` (although hacky this eases the Fragment->callbacks association a lot)
private _callbacks;
constructor() {
super();
return global.__native(this);
}
public onHiddenChanged(hidden: boolean): void {
this._callbacks.onHiddenChanged(this, hidden, super.onHiddenChanged);
}
public onCreateAnimator(transit: number, enter: boolean, nextAnim: number): android.animation.Animator {
let result = this._callbacks.onCreateAnimator(this, transit, enter, nextAnim, super.onCreateAnimator);
return result;
}
public onCreate(savedInstanceState: android.os.Bundle) {
super.setHasOptionsMenu(true);
this._callbacks.onCreate(this, savedInstanceState, super.onCreate);
}
public onCreateView(inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle) {
let result = this._callbacks.onCreateView(this, inflater, container, savedInstanceState, super.onCreateView);
return result;
}
public onSaveInstanceState(outState: android.os.Bundle) {
this._callbacks.onSaveInstanceState(this, outState, super.onSaveInstanceState);
}
public onDestroyView() {
this._callbacks.onDestroyView(this, super.onDestroyView);
}
public onDestroy() {
this._callbacks.onDestroy(this, super.onDestroy);
}
public toString(): string {
return this._callbacks.toStringOverride(this, super.toString);
}
}
frame.setFragmentClass(FragmentClass);

View File

@ -17,12 +17,14 @@ let navDepth = -1;
let fragmentId = -1; let fragmentId = -1;
let activityInitialized = false; let activityInitialized = false;
const PAGE_FRAGMENT_TAG = "_fragmentTag"; const PAGE_FRAGMENT_TAG = "_fragmentTag";
const CALLBACKS = "_callbacks";
function onFragmentShown(fragment: FragmentClass) { function onFragmentShown(fragment: android.app.Fragment) {
if (trace.enabled) { if (trace.enabled) {
trace.write(`SHOWN ${fragment}`, trace.categories.NativeLifecycle); trace.write(`SHOWN ${fragment}`, trace.categories.NativeLifecycle);
} }
if (fragment.clearHistory) { let callbacks: FragmentCallbacksImplementation = fragment[CALLBACKS];
if (callbacks.clearHistory) {
// This is the fragment which was at the bottom of the stack (fragment0) when we cleared history and called // This is the fragment which was at the bottom of the stack (fragment0) when we cleared history and called
// manager.popBackStack(firstEntryName, android.app.FragmentManager.POP_BACK_STACK_INCLUSIVE); // manager.popBackStack(firstEntryName, android.app.FragmentManager.POP_BACK_STACK_INCLUSIVE);
if (trace.enabled) { if (trace.enabled) {
@ -33,9 +35,9 @@ function onFragmentShown(fragment: FragmentClass) {
// TODO: consider putting entry and page in queue so we can safely extract them here. Pass the index of current navigation and extract it from here. // TODO: consider putting entry and page in queue so we can safely extract them here. Pass the index of current navigation and extract it from here.
// After extracting navigation info - remove this index from navigation stack. // After extracting navigation info - remove this index from navigation stack.
var frame = fragment.frame; let frame = callbacks.frame;
var entry = fragment.entry; let entry = callbacks.entry;
var page = entry.resolvedPage; let page = entry.resolvedPage;
page[PAGE_FRAGMENT_TAG] = entry.fragmentTag; page[PAGE_FRAGMENT_TAG] = entry.fragmentTag;
let currentNavigationContext; let currentNavigationContext;
@ -63,14 +65,14 @@ function onFragmentShown(fragment: FragmentClass) {
transitionModule._onFragmentShown(fragment, isBack); transitionModule._onFragmentShown(fragment, isBack);
} }
function onFragmentHidden(fragment: FragmentClass, destroyed: boolean) { function onFragmentHidden(fragment: android.app.Fragment, destroyed: boolean) {
if (trace.enabled) { if (trace.enabled) {
trace.write(`HIDDEN ${fragment}; destroyed: ${destroyed}`, trace.categories.NativeLifecycle); trace.write(`HIDDEN ${fragment}; destroyed: ${destroyed}`, trace.categories.NativeLifecycle);
} }
let callbacks: FragmentCallbacksImplementation = fragment[CALLBACKS];
var isBack = fragment.entry.isBack; let isBack = callbacks.entry.isBack;
fragment.entry.isBack = undefined; callbacks.entry.isBack = undefined;
fragment.entry.resolvedPage[PAGE_FRAGMENT_TAG] = undefined; callbacks.entry.resolvedPage[PAGE_FRAGMENT_TAG] = undefined;
// Handle page transitions. // Handle page transitions.
transitionModule._onFragmentHidden(fragment, isBack, destroyed); transitionModule._onFragmentHidden(fragment, isBack, destroyed);
@ -136,10 +138,10 @@ export class Frame extends frameCommon.Frame {
let manager = activity.getFragmentManager(); let manager = activity.getFragmentManager();
// Current Fragment // Current Fragment
var currentFragment: FragmentClass; let currentFragment: android.app.Fragment;
if (this._currentEntry) { if (this._currentEntry) {
this._currentEntry.isNavigation = true; this._currentEntry.isNavigation = true;
currentFragment = <FragmentClass>manager.findFragmentByTag(this._currentEntry.fragmentTag); currentFragment = manager.findFragmentByTag(this._currentEntry.fragmentTag);
} }
let clearHistory = backstackEntry.entry.clearHistory; let clearHistory = backstackEntry.entry.clearHistory;
@ -150,13 +152,18 @@ export class Frame extends frameCommon.Frame {
} }
navDepth++; navDepth++;
fragmentId++; fragmentId++;
var newFragmentTag = `fragment${fragmentId}[${navDepth}]`; let newFragmentTag = `fragment${fragmentId}[${navDepth}]`;
let newFragment = new FragmentClass(); ensureFragmentClass();
let newFragment: android.app.Fragment = new fragmentClass();
let callbacks = new FragmentCallbacksImplementation();
callbacks.frame = this;
callbacks.entry = backstackEntry;
newFragment[CALLBACKS] = callbacks;
let args = new android.os.Bundle(); let args = new android.os.Bundle();
args.putInt(FRAMEID, this._android.frameId); args.putInt(FRAMEID, this._android.frameId);
newFragment.setArguments(args); newFragment.setArguments(args);
newFragment.frame = this;
newFragment.entry = backstackEntry;
// backstackEntry // backstackEntry
backstackEntry.isNavigation = true; backstackEntry.isNavigation = true;
@ -185,7 +192,7 @@ export class Frame extends frameCommon.Frame {
let emptyNativeBackStack = clearHistory && length > 0; let emptyNativeBackStack = clearHistory && length > 0;
if (emptyNativeBackStack) { if (emptyNativeBackStack) {
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
let fragmentToRemove = <FragmentClass>manager.findFragmentByTag(manager.getBackStackEntryAt(i).getName()); let fragmentToRemove = manager.findFragmentByTag(manager.getBackStackEntryAt(i).getName());
Frame._clearHistory(fragmentToRemove); Frame._clearHistory(fragmentToRemove);
} }
if (currentFragment) { if (currentFragment) {
@ -219,7 +226,7 @@ export class Frame extends frameCommon.Frame {
// Add newFragment // Add newFragment
if (trace.enabled) { if (trace.enabled) {
trace.write(`\tADD ${newFragmentTag}<${newFragment.entry.resolvedPage}>`, trace.categories.Navigation); trace.write(`\tADD ${newFragmentTag}<${callbacks.entry.resolvedPage}>`, trace.categories.Navigation);
} }
fragmentTransaction.add(this.containerViewId, newFragment, newFragmentTag); fragmentTransaction.add(this.containerViewId, newFragment, newFragmentTag);
@ -257,14 +264,15 @@ export class Frame extends frameCommon.Frame {
} }
} }
private static _clearHistory(fragment: FragmentClass) { private static _clearHistory(fragment: android.app.Fragment) {
if (trace.enabled) { if (trace.enabled) {
trace.write(`CLEAR HISTORY FOR ${fragment}`, trace.categories.Navigation); trace.write(`CLEAR HISTORY FOR ${fragment}`, trace.categories.Navigation);
} }
fragment.clearHistory = true; let callbacks: FragmentCallbacksImplementation = fragment[CALLBACKS];
callbacks.clearHistory = true; // This is hacky
transitionModule._clearBackwardTransitions(fragment); transitionModule._clearBackwardTransitions(fragment);
transitionModule._clearForwardTransitions(fragment); transitionModule._clearForwardTransitions(fragment);
transitionModule._removePageNativeViewFromAndroidParent(fragment.entry.resolvedPage); transitionModule._removePageNativeViewFromAndroidParent(callbacks.entry.resolvedPage);
} }
public _goBackCore(backstackEntry: definition.BackstackEntry) { public _goBackCore(backstackEntry: definition.BackstackEntry) {
@ -571,8 +579,9 @@ function findPageForFragment(fragment: android.app.Fragment, frame: Frame) {
} }
if (page) { if (page) {
(<any>fragment).frame = frame; let callbacks: FragmentCallbacksImplementation = fragment[CALLBACKS];
(<any>fragment).entry = entry; callbacks.frame = frame;
callbacks.entry = entry;
} }
else { else {
//throw new Error(`Could not find a page for ${fragmentTag}.`); //throw new Error(`Could not find a page for ${fragmentTag}.`);
@ -608,31 +617,46 @@ function ensureAnimationFixed() {
} }
} }
@JavaProxy("com.tns.FragmentClass") function ensureFragmentClass() {
class FragmentClass extends android.app.Fragment { if(fragmentClass) {
return;
}
// this require will apply the FragmentClass implementation
require("ui/frame/fragment");
if(!fragmentClass) {
throw new Error("Failed to initialize the extended android.app.Fragment class");
}
}
let fragmentClass: any;
export function setFragmentClass(clazz: any) {
if(fragmentClass) {
throw new Error("Fragment class already initialized");
}
fragmentClass = clazz;
}
class FragmentCallbacksImplementation implements definition.AndroidFragmentCallbacks {
public frame: Frame; public frame: Frame;
public entry: definition.BackstackEntry; public entry: definition.BackstackEntry;
public clearHistory: boolean; public clearHistory: boolean;
constructor() { public onHiddenChanged(fragment: android.app.Fragment, hidden: boolean, superFunc: Function): void {
super();
return global.__native(this);
}
public onHiddenChanged(hidden: boolean): void {
if (trace.enabled) { if (trace.enabled) {
trace.write(`${this}.onHiddenChanged(${hidden})`, trace.categories.NativeLifecycle); trace.write(`${fragment}.onHiddenChanged(${hidden})`, trace.categories.NativeLifecycle);
} }
super.onHiddenChanged(hidden); superFunc.call(fragment, hidden);
if (hidden) { if (hidden) {
onFragmentHidden(this, false); onFragmentHidden(fragment, false);
} }
else { else {
onFragmentShown(this); onFragmentShown(fragment);
} }
} }
public onCreateAnimator(transit: number, enter: boolean, nextAnim: number): android.animation.Animator { public onCreateAnimator(fragment: android.app.Fragment, transit: number, enter: boolean, nextAnim: number, superFunc: Function): android.animation.Animator {
var nextAnimString: string; var nextAnimString: string;
switch (nextAnim) { switch (nextAnim) {
case -10: nextAnimString = "enter"; break; case -10: nextAnimString = "enter"; break;
@ -641,86 +665,86 @@ class FragmentClass extends android.app.Fragment {
case -40: nextAnimString = "popExit"; break; case -40: nextAnimString = "popExit"; break;
} }
var animator = transitionModule._onFragmentCreateAnimator(this, nextAnim); var animator = transitionModule._onFragmentCreateAnimator(fragment, nextAnim);
if (!animator) { if (!animator) {
animator = super.onCreateAnimator(transit, enter, nextAnim); animator = superFunc.call(fragment, transit, enter, nextAnim);
} }
if (trace.enabled) { if (trace.enabled) {
trace.write(`${this}.onCreateAnimator(${transit}, ${enter ? "enter" : "exit"}, ${nextAnimString}): ${animator}`, trace.categories.NativeLifecycle); trace.write(`${fragment}.onCreateAnimator(${transit}, ${enter ? "enter" : "exit"}, ${nextAnimString}): ${animator}`, trace.categories.NativeLifecycle);
} }
return animator; return animator;
} }
public onCreate(savedInstanceState: android.os.Bundle): void { public onCreate(fragment: android.app.Fragment, savedInstanceState: android.os.Bundle, superFunc: Function): void {
if (trace.enabled) { if (trace.enabled) {
trace.write(`${this}.onCreate(${savedInstanceState})`, trace.categories.NativeLifecycle); trace.write(`${fragment}.onCreate(${savedInstanceState})`, trace.categories.NativeLifecycle);
} }
super.onCreate(savedInstanceState);
super.setHasOptionsMenu(true); superFunc.call(fragment, savedInstanceState);
// There is no entry set to the fragment, so this must be destroyed fragment that was recreated by Android. // There is no entry set to the fragment, so this must be destroyed fragment that was recreated by Android.
// We should find its corresponding page in our backstack and set it manually. // We should find its corresponding page in our backstack and set it manually.
if (!this.entry) { if (!this.entry) {
let frameId = this.getArguments().getInt(FRAMEID); let frameId = fragment.getArguments().getInt(FRAMEID);
let frame = getFrameById(frameId); let frame = getFrameById(frameId);
if (frame) { if (frame) {
this.frame = frame; this.frame = frame;
} }
else { else {
throw new Error(`Cannot find Frame for ${this}`); throw new Error(`Cannot find Frame for ${fragment}`);
} }
findPageForFragment(this, this.frame); findPageForFragment(fragment, this.frame);
} }
} }
public onCreateView(inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle): android.view.View { public onCreateView(fragment: android.app.Fragment, inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle, superFunc: Function): android.view.View {
if (trace.enabled) { if (trace.enabled) {
trace.write(`${this}.onCreateView(inflater, container, ${savedInstanceState})`, trace.categories.NativeLifecycle); trace.write(`${fragment}.onCreateView(inflater, container, ${savedInstanceState})`, trace.categories.NativeLifecycle);
} }
var entry = this.entry; var entry = this.entry;
var page = entry.resolvedPage; var page = entry.resolvedPage;
if (savedInstanceState && savedInstanceState.getBoolean(HIDDEN, false)) { if (savedInstanceState && savedInstanceState.getBoolean(HIDDEN, false)) {
this.getFragmentManager().beginTransaction().hide(this).commit(); fragment.getFragmentManager().beginTransaction().hide(fragment).commit();
page._onAttached(this.getActivity()); page._onAttached(fragment.getActivity());
} }
else { else {
onFragmentShown(this); onFragmentShown(fragment);
} }
return page._nativeView; return page._nativeView;
} }
public onSaveInstanceState(outState: android.os.Bundle): void { public onSaveInstanceState(fragment: android.app.Fragment, outState: android.os.Bundle, superFunc: Function): void {
if (trace.enabled) { if (trace.enabled) {
trace.write(`${this}.onSaveInstanceState(${outState})`, trace.categories.NativeLifecycle); trace.write(`${fragment}.onSaveInstanceState(${outState})`, trace.categories.NativeLifecycle);
} }
super.onSaveInstanceState(outState); superFunc.call(fragment, outState);
if (this.isHidden()) { if (fragment.isHidden()) {
outState.putBoolean(HIDDEN, true); outState.putBoolean(HIDDEN, true);
} }
} }
public onDestroyView(): void { public onDestroyView(fragment: android.app.Fragment, superFunc: Function): void {
if (trace.enabled) { if (trace.enabled) {
trace.write(`${this}.onDestroyView()`, trace.categories.NativeLifecycle); trace.write(`${fragment}.onDestroyView()`, trace.categories.NativeLifecycle);
} }
super.onDestroyView(); superFunc.call(fragment);
// Detaching the page has been move in onFragmentHidden due to transitions. // Detaching the page has been move in onFragmentHidden due to transitions.
onFragmentHidden(this, true); onFragmentHidden(fragment, true);
} }
public onDestroy(): void { public onDestroy(fragment: android.app.Fragment, superFunc: Function): void {
if (trace.enabled) { if (trace.enabled) {
trace.write(`${this}.onDestroy()`, trace.categories.NativeLifecycle); trace.write(`${fragment}.onDestroy()`, trace.categories.NativeLifecycle);
} }
super.onDestroy(); superFunc.call(fragment);
} }
public toString(): string { public toStringOverride(fragment: android.app.Fragment, superFunc: Function): string {
return `${this.getTag()}<${this.entry ? this.entry.resolvedPage : ""}>`; return `${fragment.getTag()}<${this.entry ? this.entry.resolvedPage : ""}>`;
} }
} }

View File

@ -124,6 +124,8 @@ declare module "ui/frame" {
export var activityCallbacks: AndroidActivityCallbacks; export var activityCallbacks: AndroidActivityCallbacks;
export function setFragmentClass(clazz: any): void;
/** /**
* Gets the topmost frame in the frames stack. An application will typically has one frame instance. Multiple frames handle nested (hierarchical) navigation scenarios. * Gets the topmost frame in the frames stack. An application will typically has one frame instance. Multiple frames handle nested (hierarchical) navigation scenarios.
*/ */
@ -315,6 +317,17 @@ declare module "ui/frame" {
onActivityResult(activity: any, requestCode: number, resultCode: number, data: any, superFunc: Function); onActivityResult(activity: any, requestCode: number, resultCode: number, data: any, superFunc: Function);
} }
export interface AndroidFragmentCallbacks {
onHiddenChanged(fragment: any, hidden: boolean, superFunc: Function): void;
onCreateAnimator(fragment: any, transit: number, enter: boolean, nextAnim: number, superFunc: Function): any;
onCreate(fragment: any, savedInstanceState: any, superFunc: Function): void;
onCreateView(fragment: any, inflater: any, container: any, savedInstanceState: any, superFunc: Function): any;
onSaveInstanceState(fragment: any, outState: any, superFunc: Function): void;
onDestroyView(fragment: any, superFunc: Function): void;
onDestroy(fragment: any, superFunc: Function): void;
toStringOverride(fragment: any, superFunc: Function): string;
}
/* tslint:disable */ /* tslint:disable */
/** /**
* Represents the iOS-specific Frame object, aggregated within the common Frame one. * Represents the iOS-specific Frame object, aggregated within the common Frame one.

View File

@ -344,8 +344,8 @@ export function _onFragmentHidden(fragment: any, isBack: boolean, destroyed: boo
function _completePageAddition(fragment: any, isBack: boolean) { function _completePageAddition(fragment: any, isBack: boolean) {
let expandedFragment = <ExpandedFragment>fragment; let expandedFragment = <ExpandedFragment>fragment;
expandedFragment.completePageAdditionWhenTransitionEnds = undefined; expandedFragment.completePageAdditionWhenTransitionEnds = undefined;
let frame = fragment.frame; let frame = fragment._callbacks.frame;
let entry: BackstackEntry = fragment.entry; let entry: BackstackEntry = fragment._callbacks.entry;
let page: Page = entry.resolvedPage; let page: Page = entry.resolvedPage;
if (trace.enabled) { if (trace.enabled) {
trace.write(`STARTING ADDITION of ${page}...`, trace.categories.Transition); trace.write(`STARTING ADDITION of ${page}...`, trace.categories.Transition);
@ -363,8 +363,8 @@ function _completePageAddition(fragment: any, isBack: boolean) {
function _completePageRemoval(fragment: any, isBack: boolean) { function _completePageRemoval(fragment: any, isBack: boolean) {
let expandedFragment = <ExpandedFragment>fragment; let expandedFragment = <ExpandedFragment>fragment;
expandedFragment.completePageRemovalWhenTransitionEnds = undefined; expandedFragment.completePageRemovalWhenTransitionEnds = undefined;
let frame = fragment.frame; let frame = fragment._callbacks.frame;
let entry: BackstackEntry = fragment.entry; let entry: BackstackEntry = fragment._callbacks.entry;
let page: Page = entry.resolvedPage; let page: Page = entry.resolvedPage;
if (trace.enabled) { if (trace.enabled) {
trace.write(`STARTING REMOVAL of ${page}...`, trace.categories.Transition); trace.write(`STARTING REMOVAL of ${page}...`, trace.categories.Transition);

View File

@ -129,6 +129,7 @@
"tns-core-modules/ui/editable-text-base/editable-text-base.ios.ts", "tns-core-modules/ui/editable-text-base/editable-text-base.ios.ts",
"tns-core-modules/ui/enums/enums.ts", "tns-core-modules/ui/enums/enums.ts",
"tns-core-modules/ui/frame/activity.android.ts", "tns-core-modules/ui/frame/activity.android.ts",
"tns-core-modules/ui/frame/fragment.android.ts",
"tns-core-modules/ui/frame/frame-common.ts", "tns-core-modules/ui/frame/frame-common.ts",
"tns-core-modules/ui/frame/frame.android.ts", "tns-core-modules/ui/frame/frame.android.ts",
"tns-core-modules/ui/frame/frame.ios.ts", "tns-core-modules/ui/frame/frame.ios.ts",