【Flutter】GoRouterBuilder+BottomNavigationBarの画面遷移で謎の画面が挟まれる

# TL;DR

BottomNavigationBarのタブに対応するページのrouterで遷移先のRouteを階層的に記述していたのが原因だった。

# 現在の環境

| パッケージ名 | バージョン |
| --- | --- |
| Flutter | 3.24.0 |
| go_router | 14.2.3 |
| go_router_builder | 2.7.1 |

# 何が起こったか

現在私のプロジェクトではBottomNavigationBarを設定しており、BodyはSwitch文でタブに対応した画面が表示されるよう実装しています。

状態管理はRiverpodを使用していますが今回本筋には絡んでこない為省略します。

```dart
class MainPage extends ConsumerWidget {
  const MainPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final mainState = ref.watch(mainNotifierProvider);

    void changePage(index) {
      final mainNotifier = ref.read(mainNotifierProvider.notifier);

      List<NavigationBarType> types = NavigationBarType.values;
      mainNotifier.changePage(types[index]);
    }

    return Scaffold(
      body: switchBody(mainState.currentPageType),
      bottomNavigationBar: BottomNavigationBar(
        type: BottomNavigationBarType.fixed,
        currentIndex: mainState.currentPageType.index,
        onTap: changePage,
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'ホーム',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.settings),
            label: '設定',
          ),
        ],
      ),
    );
  }
}

Widget switchBody(NavigationBarType currentPageType) {
  switch (currentPageType) {
    case NavigationBarType.home:
      return const HomePage();
    case NavigationBarType.setting:
      return const SettingPage();
    default:
      return const HomePage();
  }
}
```

またルーティングはGo Build Routerを使用しており、ルーティングの設定は以下となります。

```dart
final routerProvider = Provider((ref) {
  return GoRouter(
    debugLogDiagnostics: true,
    initialLocation: '/',
    redirect: (context, state) async {
      return null;
    },
    errorBuilder: (context, state) {
      return ErrorRoute(error: state.error!).build(context, state);
    },
    routes: $appRoutes,
  );
});

class ErrorRoute extends GoRouteData {
  ErrorRoute({required this.error});

  final Exception error;

  @override
  Widget build(BuildContext context, GoRouterState state) {
    return ErrorMessageWidget(
      title: 'エラーが発生しました。',
      message: '時間を置いて再度お試しください。',
      errorMessage: error.toString(),
    );
  }
}

@TypedGoRoute<MainRoute>(
  path: '/',
  name: 'main',
  routes: 
    TypedGoRoute<HomeRoute>(
      path: 'home',
      name: 'home',
      routes: [
        TypedGoRoute<HomeDetailRoute>(
          path: 'detail',
          name: 'home_detail',
        ),
      ],
    ),
    TypedGoRoute<SettingRoute>(
      path: 'setting',
      name: 'setting',
    ),
  ],
)
class MainRoute extends GoRouteData {
  const MainRoute();

  @override
  Widget build(BuildContext context, GoRouterState state) {
    return const MainPage();
  }
}

class HomeRoute extends GoRouteData {
  const HomeRoute();

  @override
  Widget build(BuildContext context, GoRouterState state) {
    return const HomePage();
  }
}

class HomeDetailRoute extends GoRouteData {
  const HomeDetailRoute();

  @override
  Widget build(BuildContext context, GoRouterState state) {
    return HomeDetailPage();
  }
}

class SettingRoute extends GoRouteData {
  const SettingRoute();

  @override
  Widget build(BuildContext context, GoRouterState state) {
    return const SettingPage();
  }
}
```

上記のホーム画面にある画面遷移ボタンをクリックする事で、以下の処理が実行され画面遷移が行われるよう実装しました。

```dart
void push() {
	HomeDetailRoute().go(context);
}
```

その際上記のボタンを押す事で正常に遷移が行えたのですが、以下の動画のように元のページに戻るまでに2回画面遷移がかかる現象が発生しました。

![bottomnavigationbar.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344828/f02f1382-96a1-8260-1a9d-ebfcb31e2d0d.gif)

## 何が原因だったか

上記の動画をよく見ると、最初にpopした際はBottomNavigationBarが表示されていないものの遷移前の画面と内容が同じ画面が表示されています。
こちらのページが挟まれる原因を調査した所、上記ルーティング設定の以下の箇所が原因でした。

```dart
TypedGoRoute<HomeRoute>(
  path: 'home',
  name: 'home'
  routes: [
    TypedGoRoute<HomeDetailRoute>(
      path: 'detail',
      name: 'home_detail',
    ),
  ],
),
```

上記設定ではホーム画面から遷移する詳細画面を定義しているのですが、こちらをホーム画面のTypedGoRouteの配下にしていた為、

1. BottomNavigationBarとそれに連動するBodyが含まれる画面
2. 本来は表示されないホーム画面
3. 詳細画面

という余計なルーティングが挟まれておりました。

その為上記のコードを以下のように修正する事で解決しました。

```dart
TypedGoRoute<HomeRoute>(
  path: 'home',
  name: 'home'
),
TypedGoRoute<HomeDetailRoute>(
  path: 'home/detail',
  name: 'home_detail',
),
```

# まとめ

GoRouterは階層的なルーティングが行えますが、本件以外でも何回か問題が発生したので並列的に記述する方が安定しそうですね。