네비게이션과 라우팅: 화면 사이를 오가기
네비게이션과 라우팅: 화면 사이를 오가기
앱이 화면 몇 개로 끝나지 않게 되면 자연스럽게 '이 화면에서 저 화면으로 어떻게 넘어가지?'라는 질문이 생겨요. 플러터는 화면 사이를 오가고 딥 링크를 처리하는 완전한 시스템을 제공해요. 복잡한 딥 링크가 필요 없는 작은 앱은 Navigator만으로 충분하고, 딥 링크 요구가 구체적인 앱은 Router까지 함께 써서 Android·iOS에서 딥 링크를 올바르게 처리하고 웹에서는 주소창과 동기화하게 만들어요.
Navigator: 화면을 스택으로 쌓기
Navigator 위젯은 화면을 스택(stack)으로 보여줘요. 대상 플랫폼에 맞는 전환 애니메이션까지 자동으로 적용하죠. 새 화면으로 가려면 라우트의 BuildContext를 통해 Navigator에 접근해 push()나 pop() 같은 메서드를 부르면 돼요.
child: const Text('Open second screen'),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (context) => const SecondScreen(),
),
);
},
Navigator는 Route 객체의 스택(곧 히스토리 스택)을 유지해요. 그래서 push()도 Route 객체를 인자로 받아요. 위 코드의 MaterialPageRoute는 Route의 서브클래스로, Material Design 표준 전환 애니메이션을 지정해요.
이름 있는 라우트(Named routes)
네비게이션과 딥 링크 요구가 단순한 앱이라면 Navigator로 이동하고, 딥 링크 처리는 MaterialApp.routes 파라미터로 해결할 수 있어요.
child: const Text('Open second screen'),
onPressed: () {
Navigator.pushNamed(context, '/second');
},
여기서 /second는 MaterialApp.routes 목록에 선언된 이름 있는 라우트(named route) 예요.
다만 이름 있는 라우트는 딥 링크를 처리할 수는 있지만 동작이 항상 같아서 커스터마이즈할 수 없어요. 새 딥 링크를 받으면 사용자가 어디에 있든 Navigator에 새 Route를 쌓을 뿐이죠. 또 이름 있는 라우트를 쓰는 앱에서는 브라우저의 앞으로 가기 버튼도 지원하지 않아요. 이런 이유로 공식 문서는 대부분의 앱에서 이름 있는 라우트를 권장하지 않아요. 대신 go_router 같은 라우팅 패키지를 쓰거나, Navigator를 MaterialPageRoute와 함께 쓰라고 조언해요.
Router와 라우팅 패키지
고급 네비게이션 요구가 있는 앱(각 화면에 직접 링크가 걸리는 웹 앱, Navigator를 여러 개 쓰는 앱 등)은 go_router 같은 라우팅 패키지를 쓰는 걸 권장해요. 이런 패키지는 라우트 경로를 파싱해서 앱이 새 딥 링크를 받을 때마다 Navigator를 설정해 주죠.
Router를 쓰려면 MaterialApp이나 CupertinoApp에서 router 생성자로 바꾸고 Router 설정을 넘겨요. go_router 같은 패키지는 라우트 설정을 제공하고, 이동은 context.go('/second')처럼 선언적으로 해요.
child: const Text('Open second screen'),
onPressed: () => context.go('/second'),
go_router 같은 패키지는 선언적(declarative) 이라서, 딥 링크를 받으면 언제나 같은 화면(들)을 보여줘요.
page-backed 라우트와 pageless 라우트
Router와 Navigator는 함께 쓰도록 설계됐어요. Router API(선언적 라우팅 패키지)로 움직이든 Navigator의 push()·pop()을 부르든, 그 이동은 결국 Navigator를 통해 이뤄져요.
Router나 선언적 패키지로 이동하면, Navigator의 각 라우트는 page-backed 돼요. 곧 Navigator 생성자의 pages 인자로 만든 Page에서 비롯된 라우트라는 뜻이죠. 반대로 Navigator.push나 showDialog로 만든 Route는 pageless 라우트가 돼요. page-backed 라우트는 항상 딥 링크가 가능하지만, pageless 라우트는 그렇지 않아요.
page-backed 라우트가 Navigator에서 제거되면 그 뒤에 있던 pageless 라우트들도 함께 제거돼요. 예를 들어 딥 링크가 page-backed 라우트를 제거하는 방식으로 이동하면, 그 뒤의 pageless 라우트들은 다음 page-backed 라우트가 나올 때까지 모두 사라져요.
웹에서의 동작
Router를 쓰는 앱은 브라우저 History API와 연동돼서 뒤로 가기·앞으로 가기 버튼과 일관된 경험을 제공해요. Router로 이동할 때마다 브라우저 히스토리 스택에 항목이 추가되고, 뒤로 버튼을 누르면 이전에 방문한 위치로 역순으로 이동해요. 사용자가 Navigator에서 페이지를 pop한 뒤 브라우저 뒤로 버튼을 누르면 이전 페이지가 스택에 다시 push되는 식이죠.
더 알아보기
- 네비게이션 레시피 모음: Flutter Cookbook — Navigation
Navigator,RouterAPI 문서: Navigator, Router- name 라우트 레시피: Navigate with named routes
- Mobile 앱이면: Deep linking