해당 글은 BottomNavigationBar 와 go_router 를 사용하는것을 기준으로 한다.
go_router 왜 사용하는가?
- Flutter 에서 직접 관리하는 package 이다. (Flutter 업데이트로 인한 지원이나 호환성 걱정이 없다)
- 앱 구조를 설계하다보면 Navigation 의 복잡함과 기본 Navigator 기능 부족으로 인한 한계성.
- 코드가 복잡해진다.. 여러 상황의 router 관리 어떻게 하죠?
...등등
BottomNavigationBar 의 문제점
- 탭 끼리의 페이지 전환이 페이지 전환이 아니다.. (BottomNavigationBar 가 같이 넘어가요!!)
- 각 탭의 라우팅 관리는 어떻게 하죠? (각 탭의 route 독립적으로 살아있나요?)
- 위의 문제로 직접 custom 하게 되는데.. (최적화 늪으로 빠진겁니다)
..등등
아무튼 여러 사유들이 있겠지만 위와 같이 문제점들이 상당히 많았다.
go_router 와 BottomNavigationBar 을 같이 사용할려 하니 더욱 문제였다.
그리고 go_router 7.1.0 버전 (2023.5.22) 에서 StatefulShellRoute 이 도입되었다.
병렬 트리(branch 개념)에 대해 별도의 내비게이터를 생성하여 상태 저장, 탐색할 수 있다.
(각 탭에 대해 탐색 상태가 있는 BottomNavigationBar 를 구현할 때 편리하다.)
기본적인 BottomNavigationBar 를 구현할때는 StatefulShellRoute.indexedStack 메서드를
커스텀하고 싶다면 StatefulShellRoute 메서드를 사용하면 된다.
아래 소스와 같이 StatefulShellRoute 를 사용하여 router 를 구성할 수 있다.
final GoRouter _router = GoRouter(
initialLocation: '/a',
routes: <RouteBase>[
StatefulShellRoute.indexedStack(
builder: (BuildContext context, GoRouterState state,
StatefulNavigationShell navigationShell) {
// 사용자 정의 셸을 구현하는 위젯을 반환합니다. (BottomNavigationBar)
return Scaffold(
body: navigationShell,
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Section A'),
BottomNavigationBarItem(icon: Icon(Icons.work), label: 'Section B'),
BottomNavigationBarItem(icon: Icon(Icons.tab), label: 'Section C'),
],
currentIndex: navigationShell.currentIndex,
onTap: (int index) {
// 브랜치를 전환하는데는 StatefulNavigationShell.goBranch 메서드를 사용한다.
navigationShell.goBranch(index: index);
},
),
);
},
// 각각 별도의 상태 저장 분기를 생성하기 위해 StatefulShellBranch 메서드를 사용한다.
branches: <StatefulShellBranch>[
// Tab A (First Tab)
StatefulShellBranch(
routes: <RouteBase>[
GoRoute(
path: '/a',
builder: (BuildContext context, GoRouterState state) =>
const RootScreen(label: 'A', detailsPath: '/a/details'),
routes: <RouteBase>[
GoRoute(
path: 'details',
builder: (BuildContext context, GoRouterState state) =>
const DetailsScreen(label: 'A'),
),
],
),
],
),
// Tab B (Second Tab)
StatefulShellBranch(
routes: <RouteBase>[
GoRoute(
path: '/b',
builder: (BuildContext context, GoRouterState state) =>
const RootScreen(
label: 'B',
detailsPath: '/b/details/1',
secondDetailsPath: '/b/details/2',
),
routes: <RouteBase>[
GoRoute(
path: 'details/:param',
builder: (BuildContext context, GoRouterState state) =>
DetailsScreen(
label: 'B',
param: state.pathParameters['param'],
),
),
],
),
],
),
// Tab C (Third Tab)
StatefulShellBranch(
routes: <RouteBase>[
GoRoute(
path: '/c',
builder: (BuildContext context, GoRouterState state) =>
const RootScreen(
label: 'C',
detailsPath: '/c/details',
),
routes: <RouteBase>[
GoRoute(
path: 'details',!
builder: (BuildContext context, GoRouterState state) =>
DetailsScreen(
label: 'C',
extra: state.extra,
),
),
],
),
],
),
],
),
],
);