Last Modified:

The specified child already has a parent. You must call removeView() on the child's parent first. #NativeScript

tns-core-modules 6.1.2 で直っているかも。 fix(frame-android): IllegalStateException: The specified child already has a parent by manoldonev · Pull Request #7948 · NativeScript/NativeScript


問題

BottomNavigation を使った android アプリケーションで HMR(Hot Module Replacement) を使うと例外が発生する場合がある。

System.err: An uncaught Exception occurred on "main" thread.
System.err: Calling js method run failed
System.err: Error: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
System.err:     android.view.ViewGroup.addViewInner(ViewGroup.java:5034)
System.err:     android.view.ViewGroup.addView(ViewGroup.java:4865)
...
System.err:
System.err: StackTrace:
System.err:     push.../node_modules/tns-core-modules/ui/bottom-navigation/bottom-navigation.js.BottomNavigation.commitCurrentTransaction(file:///node_modules/tns-core-modules/ui/bottom-navigation/bottom-navigation.js:327:0)
System.err:     at push.../node_modules/tns-core-modules/ui/bottom-navigation/bottom-navigation.js.BottomNavigation.changeTab(file:///node_modules/tns-core-modules/ui/bottom-navigation/bottom-navigation.js:341:0)
System.err:     at push.../node_modules/tns-core-modules/ui/bottom-navigation/bottom-navigation.js.BottomNavigation.onLoaded(file:///node_modules/tns-core-modules/ui/bottom-navigation/bottom-navigation.js:267:0)
System.err:     at (file:///node_modules/tns-core-modules/ui/core/view-base/view-base.js:312:75)
...

原因

原因ははっきり分からないけれど、 BottomNavigation#{selectedIndex!=0} => TabContentItem => Frame => Page の構造があるときに発生することを確認。

<BottomNavigation selectedIndex="1" xmlns="http://schemas.nativescript.org/tns.xsd">
  <TabStrip>
    <TabStripItem>
      <Label text="0"/>
    </TabStripItem>
    <TabStripItem>
      <Label text="1"/>
    </TabStripItem>
  </TabStrip>
  <TabContentItem>
    <Frame defaultPage="page0"  />
  </TabContentItem>
  <TabContentItem>
    <Frame defaultPage="page1"  />
  </TabContentItem>
</BottomNavigation>

HMR 後に表示中のページ({selectedIndex!=0})をもう一度表示させようとしたときに selectedIndexChangedEvent がガチャガチャ飛んで、例外が発生している感じ。 tns-core-modules/ui/frame/frame.android.js#reloadPage あたりで、表示中の Frame が正しく解放されてないんじゃないかと思うけど、解決できなかった。

解決(応急処置)

BottomNavigation#onLoaded の前後で selectedIndex=0 → selectedIndex=選択したいタブ とすれば例外は発生しなくなったのでとりあえずこれで。

if (bottomNavigation.android) {

  if (!BottomNavigation.prototype['onLoadedOverridden']) {

    let lastSelectedIndex = 0;

    BottomNavigation.prototype['_onRootViewResetOverridden'] = BottomNavigation.prototype._onRootViewReset;
    BottomNavigation.prototype._onRootViewReset = function () {
      lastSelectedIndex = this.selectedIndex;
      return BottomNavigation.prototype['_onRootViewResetOverridden'].call(this);
    }

    BottomNavigation.prototype['onLoadedOverridden'] = BottomNavigation.prototype.onLoaded;
    BottomNavigation.prototype.onLoaded = function () {
      if (this._attachedToWindow) {
        this.selectedIndex = 0;
      }
      const retVal = BottomNavigation.prototype['onLoadedOverridden'].call(this);
      if (this._attachedToWindow) {
        this.selectedIndex = lastSelectedIndex;
      }
      return retVal;
    };

  }

}