GoRouter에는 ShellRoute라는 클래스가 있는데 이걸 이용해서 모바일, 웹에서 NestedRoute를 구현할 수 있다.
개인적으로 모바일 환경에서 구현된 NestedRoute의 예제는 구글링을 통해 쉽게 찾을 수 있는 듯 해서 Web환경에서의 NestedRoute를 예제로 가져와봤다.
참조!
예제 코드 실행 시 빌드 플랫폼을 웹으로 지정해서 빌드해야 함.
우선 라우터 세팅을 다음과 같이 작성한다.
// routes.dart
final GlobalKey<NavigatorState> rootNavKey = GlobalKey<NavigatorState>();
final GlobalKey<NavigatorState> shellNavKey = GlobalKey<NavigatorState>();
final GoRouter router = GoRouter(
navigatorKey: rootNavKey,
initialLocation: '/',
routes: [
GoRoute(
path: '/',
pageBuilder: (context, state) => const NoTransitionPage(child: Home()),
),
ShellRoute(
navigatorKey: shellNavKey,
routes: [
GoRoute(
path: '/somepage1',
pageBuilder: (context, state) => const NoTransitionPage(child: SomePage1()),
),
GoRoute(
path: '/somepage2',
pageBuilder: (context, state) => const NoTransitionPage(child: SomePage2()),
),
],
builder: (context, state, child) => Shell(state: state, child: child),
),
],
);
여기서 알아둬야할 것은 총 두 가지다.
1. rootNavKey
와 shellNavKey
를 이용해 NavigatorState를 둘로 나눠 각각 최상위의 GoRouter와 ShellRoute에 하나씩 key로 지정한다.
2. ShellRoute의 builder
프로퍼티가 가진 child 매개변수는 ShellRoute의 route
프로퍼티의 라우트를 통해 렌더되는 위젯이다.
이제 ShellRoute에서 라우팅 되는 위젯을 감싸는 위젯을 생성한다.
//shell.dart
class Shell extends StatelessWidget {
const Shell({super.key, required this.state, required this.child});
final GoRouterState state;
final Widget child;
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
Container(
width: 300.0,
color: Colors.black,
padding: const EdgeInsets.all(16),
child: Column(
children: [
ListTile(
onTap: () => context.go('/somepage1'),
title: const Text('Go to SomePage 1', style: TextStyle(color: Colors.white)),
),
ListTile(
onTap: () => context.go('/somepage2'),
title: const Text('Go to SomePage 2', style: TextStyle(color: Colors.white)),
),
],
),
),
Expanded(child: child),
],
),
);
}
}
Shell
위젯 생성시 전달된 child
를 통해 라우팅 경로에 따라 위젯을 렌더한다.
이때, Expanded
로 감싸진 child
만 변경된다.
이제 ShellRoute의 routes
와 연결할 수 있도록 SomePage를 작성한다.
//some_page.dart
class SomePage1 extends StatelessWidget {
const SomePage1({super.key});
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text('Some Page 1'),
),
);
}
}
class SomePage2 extends StatelessWidget {
const SomePage2({super.key});
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text('Some Page 2'),
),
);
}
}
SomePage1과 SomePage2 위젯은 라우팅 경로에 따라 ShellRoute에 nested되서 화면에 나타난다.
마지막으로 나머지 코드를 작성해준다.
// main.dart
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: router,
);
}
}
//home.dart
class Home extends StatelessWidget {
const Home({super.key});
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: InkWell(
onTap: () => context.go('/somepage1'),
child: const Text('Click!'),
),
),
);
}
}
전체 코드는 아래와 같다.
void main() => runApp(const MyApp());
final GlobalKey<NavigatorState> rootNavKey = GlobalKey<NavigatorState>();
final GlobalKey<NavigatorState> shellNavKey = GlobalKey<NavigatorState>();
final GoRouter router = GoRouter(
navigatorKey: rootNavKey,
initialLocation: '/',
routes: [
GoRoute(
path: '/',
pageBuilder: (context, state) => const NoTransitionPage(child: Home()),
),
ShellRoute(
navigatorKey: shellNavKey,
routes: [
GoRoute(
path: '/somepage1',
pageBuilder: (context, state) => const NoTransitionPage(child: SomePage1()),
),
GoRoute(
path: '/somepage2',
pageBuilder: (context, state) => const NoTransitionPage(child: SomePage2()),
),
],
builder: (context, state, child) => Shell(state: state, child: child),
),
],
);
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: router,
);
}
}
class Shell extends StatelessWidget {
const Shell({super.key, required this.state, required this.child});
final GoRouterState state;
final Widget child;
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
Container(
width: 300.0,
color: Colors.black,
padding: const EdgeInsets.all(16),
child: Column(
children: [
ListTile(
onTap: () => context.go('/somepage1'),
title: const Text('Go to SomePage 1', style: TextStyle(color: Colors.white)),
),
ListTile(
onTap: () => context.go('/somepage2'),
title: const Text('Go to SomePage 2', style: TextStyle(color: Colors.white)),
),
],
),
),
Expanded(child: child),
],
),
);
}
}
class Home extends StatelessWidget {
const Home({super.key});
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: InkWell(
onTap: () => context.go('/somepage1'),
child: const Text('Click!'),
),
),
);
}
}
class SomePage1 extends StatelessWidget {
const SomePage1({super.key});
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text('Some Page 1'),
),
);
}
}
class SomePage2 extends StatelessWidget {
const SomePage2({super.key});
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text('Some Page 2'),
),
);
}
}