FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

fix: nested fragments interact thru child fragment manager (#6293) · NativeScript/NativeScript@3071720 · GitHub

Commit 3071720

Browse files
authored
fix: nested fragments interact thru child fragment manager (#6293)
1 parent d91bfd8 commit 3071720

5 files changed

Lines changed: 125 additions & 17 deletions

File tree

‎tests/app/ui/tab-view/tab-view-root-tests.ts‎

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
import * as helper from "../helper";
21
import TKUnit = require("../../TKUnit");
3-
import { isIOS, isAndroid } from "tns-core-modules/platform";
2+
import { isAndroid } from "tns-core-modules/platform";
43
import { _resetRootView } from "tns-core-modules/application/";
54
import { Frame, NavigationEntry, topmost } from "tns-core-modules/ui/frame";
65
import { Page } from "tns-core-modules/ui/page";
76
import { TabView, TabViewItem } from "tns-core-modules/ui/tab-view";
87

98
function waitUntilNavigatedToMaxTimeout(pages: Page[], action: Function) {
10-
const maxTimeout = 5;
9+
const maxTimeout = 8;
1110
let completed = 0;
1211
function navigatedTo(args) {
1312
args.object.page.off("navigatedTo", navigatedTo);
@@ -67,13 +66,19 @@ export function test_frame_topmost_matches_selectedIndex() {
6766
create: () => tabView
6867
};
6968

70-
waitUntilNavigatedToMaxTimeout([items[0].page], () => _resetRootView(entry));
71-
72-
TKUnit.assertEqual(topmost().id, "Tab0 Frame0");
73-
74-
waitUntilNavigatedToMaxTimeout([items[1].page], () => tabView.selectedIndex = 1);
75-
76-
TKUnit.assertEqual(topmost().id, "Tab1 Frame1");
69+
if (isAndroid) {
70+
waitUntilNavigatedToMaxTimeout([items[0].page, items[1].page], () => _resetRootView(entry));
71+
TKUnit.assertEqual(topmost().id, "Tab0 Frame0");
72+
73+
tabView.selectedIndex = 1;
74+
TKUnit.assertEqual(topmost().id, "Tab1 Frame1");
75+
} else {
76+
waitUntilNavigatedToMaxTimeout([items[0].page], () => _resetRootView(entry));
77+
TKUnit.assertEqual(topmost().id, "Tab0 Frame0");
78+
79+
waitUntilNavigatedToMaxTimeout([items[1].page], () => tabView.selectedIndex = 1);
80+
TKUnit.assertEqual(topmost().id, "Tab1 Frame1");
81+
}
7782
}
7883

7984
export function test_offset_zero_should_raise_same_events() {

‎tns-core-modules/ui/core/view/view.android.ts‎

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ export class View extends ViewCommon {
234234
private layoutChangeListenerIsSet: boolean;
235235
private layoutChangeListener: android.view.View.OnLayoutChangeListener;
236236
private _manager: android.support.v4.app.FragmentManager;
237+
private _rootManager: android.support.v4.app.FragmentManager;
237238

238239
nativeViewProtected: android.view.View;
239240

@@ -265,20 +266,52 @@ export class View extends ViewCommon {
265266
}
266267
}
267268

269+
public _getChildFragmentManager(): android.support.v4.app.FragmentManager {
270+
return null;
271+
}
272+
273+
public _getRootFragmentManager(): android.support.v4.app.FragmentManager {
274+
if (!this._rootManager && this._context) {
275+
this._rootManager = (<android.support.v4.app.FragmentActivity>this._context).getSupportFragmentManager();
276+
}
277+
278+
return this._rootManager;
279+
}
280+
268281
public _getFragmentManager(): android.support.v4.app.FragmentManager {
269282
let manager = this._manager;
270283
if (!manager) {
271284
let view: View = this;
285+
let frameOrTabViewItemFound = false;
272286
while (view) {
287+
// when interacting with nested fragments instead of using getSupportFragmentManager
288+
// we must always use getChildFragmentManager instead;
289+
// we have three sources of fragments -- Frame fragments, TabViewItem fragments, and
290+
// modal dialog fragments
291+
292+
// modal -> frame / tabview (frame / tabview use modal CHILD fm)
273293
const dialogFragment = view._dialogFragment;
274294
if (dialogFragment) {
275295
manager = dialogFragment.getChildFragmentManager();
276296
break;
277-
} else {
278-
// the case is needed because _dialogFragment is on View
279-
// but parent may be ViewBase.
280-
view = view.parent as View;
281297
}
298+
299+
// - frame1 -> frame2 (frame2 uses frame1 CHILD fm)
300+
// - tabview -> frame1 (frame1 uses tabview item CHILD fm)
301+
// - frame1 -> tabview (tabview uses frame1 CHILD fm)
302+
// - frame1 -> tabview -> frame2 (tabview uses frame1 CHILD fm; frame2 uses tabview item CHILD fm)
303+
if (view._hasFragments) {
304+
if (frameOrTabViewItemFound) {
305+
manager = view._getChildFragmentManager();
306+
break;
307+
}
308+
309+
frameOrTabViewItemFound = true;
310+
}
311+
312+
// the case is needed because _dialogFragment is on View
313+
// but parent may be ViewBase.
314+
view = view.parent as View;
282315
}
283316

284317
if (!manager && this._context) {
@@ -294,6 +327,7 @@ export class View extends ViewCommon {
294327
@profile
295328
public onLoaded() {
296329
this._manager = null;
330+
this._rootManager = null;
297331
super.onLoaded();
298332
this.setOnTouchListener();
299333
}
@@ -307,6 +341,7 @@ export class View extends ViewCommon {
307341
}
308342

309343
this._manager = null;
344+
this._rootManager = null;
310345
super.onUnloaded();
311346
}
312347

@@ -405,6 +440,10 @@ export class View extends ViewCommon {
405440
return false;
406441
}
407442

443+
get _hasFragments(): boolean {
444+
return false;
445+
}
446+
408447
public layoutNativeView(left: number, top: number, right: number, bottom: number): void {
409448
if (this.nativeViewProtected) {
410449
this.nativeViewProtected.layout(left, top, right, bottom);
@@ -571,7 +610,7 @@ export class View extends ViewCommon {
571610
this._dialogFragment = df;
572611
this._raiseShowingModallyEvent();
573612

574-
this._dialogFragment.show(parent._getFragmentManager(), this._domId.toString());
613+
this._dialogFragment.show(parent._getRootFragmentManager(), this._domId.toString());
575614
}
576615

577616
protected _hideNativeModalView(parent: View) {

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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ export class Frame extends FrameBase {
121121
return this._android;
122122
}
123123

124+
get _hasFragments(): boolean {
125+
return true;
126+
}
127+
124128
_onAttachedToWindow(): void {
125129
super._onAttachedToWindow();
126130
this._attachedToWindow = true;
@@ -175,7 +179,16 @@ export class Frame extends FrameBase {
175179
}
176180
}
177181

178-
_onRootViewReset(): void {
182+
public _getChildFragmentManager() {
183+
const backstackEntry = this._executingEntry || this._currentEntry;
184+
if (backstackEntry && backstackEntry.fragment && backstackEntry.fragment.isAdded()) {
185+
return backstackEntry.fragment.getChildFragmentManager();
186+
}
187+
188+
return null;
189+
}
190+
191+
public _onRootViewReset(): void {
179192
this.disposeCurrentFragment();
180193
super._onRootViewReset();
181194
}
@@ -186,7 +199,14 @@ export class Frame extends FrameBase {
186199
}
187200

188201
private disposeCurrentFragment(): void {
189-
if (!this._currentEntry || !this._currentEntry.fragment) {
202+
// when interacting with nested fragments it seems Android is smart enough
203+
// to automatically remove child fragments when parent fragment is removed;
204+
// however, we must add a fragment.isAdded() guard as our logic will try to
205+
// explicitly remove the already removed child fragment causing an
206+
// IllegalStateException: Fragment has not been attached yet.
207+
if (!this._currentEntry ||
208+
!this._currentEntry.fragment ||
209+
!this._currentEntry.fragment.isAdded()) {
190210
return;
191211
}
192212

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ export class Frame extends View {
125125
* @private
126126
*/
127127
_currentEntry: BackstackEntry;
128+
/**
129+
* @private
130+
*/
131+
_executingEntry: BackstackEntry;
128132
/**
129133
* @private
130134
*/

‎tns-core-modules/ui/tab-view/tab-view.android.ts‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,10 @@ export class TabViewItem extends TabViewItemBase {
260260
public index: number;
261261
private _defaultTransformationMethod: android.text.method.TransformationMethod;
262262

263+
get _hasFragments(): boolean {
264+
return true;
265+
}
266+
263267
public initNativeView(): void {
264268
super.initNativeView();
265269
if (this.nativeViewProtected) {
@@ -297,6 +301,24 @@ export class TabViewItem extends TabViewItemBase {
297301
}
298302
}
299303

304+
public _getChildFragmentManager(): android.support.v4.app.FragmentManager {
305+
const tabView = this.parent as TabView;
306+
let tabFragment = null;
307+
const fragmentManager = tabView._getFragmentManager();
308+
for (let fragment of (<Array<any>>fragmentManager.getFragments().toArray())) {
309+
if (fragment.index === this.index) {
310+
tabFragment = fragment;
311+
break;
312+
}
313+
}
314+
315+
if (!tabFragment) {
316+
throw new Error(`Could not get child fragment manager for tab item with index ${this.index}`);
317+
}
318+
319+
return tabFragment.getChildFragmentManager();
320+
}
321+
300322
[fontSizeProperty.getDefault](): { nativeSize: number } {
301323
return { nativeSize: this.nativeViewProtected.getTextSize() };
302324
}
@@ -361,6 +383,10 @@ export class TabView extends TabViewBase {
361383
tabs.push(new WeakRef(this));
362384
}
363385

386+
get _hasFragments(): boolean {
387+
return true;
388+
}
389+
364390
public onItemsChanged(oldItems: TabViewItem[], newItems: TabViewItem[]): void {
365391
super.onItemsChanged(oldItems, newItems);
366392

@@ -512,6 +538,20 @@ export class TabView extends TabViewBase {
512538
return false;
513539
}
514540

541+
public _onRootViewReset(): void {
542+
this.disposeCurrentFragments();
543+
super._onRootViewReset();
544+
}
545+
546+
private disposeCurrentFragments(): void {
547+
const fragmentManager = this._getFragmentManager();
548+
const transaction = fragmentManager.beginTransaction();
549+
for (let fragment of (<Array<any>>fragmentManager.getFragments().toArray())) {
550+
transaction.remove(fragment);
551+
}
552+
transaction.commitNowAllowingStateLoss();
553+
}
554+
515555
private shouldUpdateAdapter(items: Array<TabViewItemDefinition>) {
516556
if (!this._pagerAdapter) {
517557
return false;

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL