플러터로 개인 프로젝트를 개발 중에 있는 상황입니다.
추후 채팅 작업이 들어갈 화면 이외의 모든 화면의 UI 작업을 완료한 상황입니다.
그래서 Navigation을 연결하는 작업을 진행하였습니다!
Go_router를 사용하여 URL Router 연결 작업을 정리 하고 한 번 더 학습하기 위해서 글을 작성합니다! 👍
기존 UI Design 작업을 진행하면서 간단한 push 방식과 List<Widget>를 활용하여 Navigation 연결을 진행하였습니다. 하지만 push 방식은 multi platform 지원이 완벽하지 않는 상황이고 다양한 상황에 대처할 수 있고 딥링크에도 URL path형식이 더 알맞다고 생각하여 GoRouter를 사용하였습니다..
Push & Pop
Go 방식을 알아보기전에 기존에 사용했던 Push 와 Pop에 대해 알아보겠습니다.
push는 Stack처럼 하나씩 쌓이고 없어지는 구조로 직관적인 형태를 갖고 있습니다.
버튼을 눌러 페이지 하나를 들어갈 때 하나씩 쌓이고 뒤로가기 버튼을 눌러 페이지가 하나씩 제거되는 방식입니다.
아래 이미지를 보시면 더욱 이해가 쉽게 됩니다!
GO 방식과 Push 방식의 차이점
둘의 차이점을 비교해봤을때 제 프로젝트에서는 Go 방식이 더욱 적합하여 채택하였습니다.
GoRouter
GoRouter는 플러터 공식 예제에서도 사용되는 package입니다.
또한 라우터 API를 사용하여 서로 다른 화면 간을 탐색할 수 있는 편리한 URL 기반 API를 제공하고 URL 패턴을 정의하고, URL을 사용하여 탐색하고, 심층 링크를 처리하고, 기타 여러 탐색 관련 시나리오를 사용할 수 있습니다.
특징
GoRouter의 특징이 여러 개 있는데 제가 사용하면서 느낀 점은 아래 정도인데 더 많은 특징이 있다고 하니(예: 리디렉션 등) 공식문서 글의 정리가 아쉽지만 공식문서에서 보시면 좋을 것 같습니다.
- 템플릿 구문을 사용하여 경로 및 쿼리 매개 변수 구문 분석(예: 'user/:id')
- 목적지(하위 경로)에 대한 여러 화면 표시
- ShellRoute를 활용한 TabBar 지원
- 전환 애니메이션 지원
StatefulShellRoute와 StatefulShellBranch를 활용한 Tab 기능 개발
기존에는 List<Widget>을 사용하여 선택된 index번째 요소를 화면에 보여지게 되었습니다.
이렇게 관리하는 것은 인덱스를 계속 관리를 해주어야 하는 문제가 있습니다.
그래서 Nested Route를 사용해서 페이지들을 관리하였습니다.
아래 직접 만든 다이어그램을 보면 root navigator 안에 StatefulShellRoute가 존재하고 4 개의 branch를 갖습니다.
StatefulNavigationShell으로 TabBar를 구성하였을때 이슈가 생겼습니다. 바로 모든 페이지에서 TabBar를 보여지는 문제입니다. 저의 화면 기획은 Detail 혹은 Edit에서는 TabBar가 표시되지 않는 UI입니다. 그래서 noNavigationBarPaths배열을 만들어 배열안 포함된 문자열이 경로상에 있으면 bottomNavigationBar를 표시하지 않게하였습니다.
@override
Widget build(BuildContext context) {
/// noNavigationBarPaths 배열에 포함된 문자열이 있으면 bottomNavigationBar를 표시하지 않습니다.
List<String> noNavigationBarPaths = ['detail', 'edit'];
bool _showNavigationBar = !noNavigationBarPaths.any((path) => navigationShell.shellRouteContext.routerState.fullPath!.contains(path));
return Scaffold(
body: navigationShell,
bottomNavigationBar: _showNavigationBar ? GreenFieldTabBar(
navigationShell: navigationShell, // 현재 선택된 인덱스 전달
) : null,
);
}
추가)
검색을 하다 보니 NavigationBar를 표시하지 않는 방법이 있었는데 추후 관리하기에 지금 방법이 더욱 쉬운 것 같아 변경하지 않겠습니다.
방법은 해당 GoRoute에 parentNavigatorKey: _rootNavigatorKey를 추가해주면 됩니다.
하지만 이방법은 결국 detail이 해당 branch key값을 갖는 게 아닌 root에 속해있는 것이니 명확하지 않아 좋은 방식은 아니라고 생각합니다.
GoRoute(
name: 'notice_detail',
path: 'detail/:id',
parentNavigatorKey: _rootNavigatorKey,
builder: (context, state) => NoticeDetailView(
notice: noticeVM
.getNoticeById(state.pathParameters['id']!)),
),
StatefulShellRoute
- StatefulShellRoute이란 하위 경로에 별도의 탐색기가 있는 UI Shell을 표시하는 경로이다.
- 즉, 중첩된 각 브렌치에 대해 별도의 탐색기를 생성하므로 상태별 중첩 탐색이 가능하다.
- 예시로 각 탭에 대해 지속적인 탐색 상태가 있는 BottomNavigationBar를 UI로 구현할 때 편리한 특성을 가고 있음.
- StatefulShellRoute.indexedStack 생성자를 사용하여 분기 내비게이터를 나타내는 위젯을 관리하는 데 사용되는 컨테이너(내비게이터 컨테이너빌더)에 대한 인덱싱스택 기반 구현을 제공한다.
StatefulShellBranch
- StatefulShellRoute을 구성하는 데 사용되는 상태 저장 탐색 트리에서 별도의 분기를 나타낸다.
- 필요한 유일한 argument는 하위 경로(경로)뿐이지만 때로는 초기 위치도 제공하는 것이 편리할 수 있다고 한다.
- 이 매개변수의 값은 분기를 처음 로드할때 사용되는데 예시로 StatefulNavigation의 goBranch 방법을 사용하여 분기를 전환할 때라고 한다.
- StatefulShellBranch를 만들 때 사용자 지정 탐색기 키를 제공할 수 있으며, 이는 탐색기를 다른 곳에서 액세스해야 할 때 유용할 수 있습니다. 키가 제공되지 않으면 기본 키가 생성됩니다. 위의 글이 무슨 뜻이나면 아래 코드 처럼 키값을 만들어서 제공할 수 있지만 제공하지 않으면 기본키가 생성되어 탐색이 된다는 것이다..! 그래서 생성을 안해줘도 되지만 특정화면이동이나 복잡한 네비게이션 상황에서는 키값이 있는것이 더욱 좋고 Navigation 상태를 독립적으로 관리하기 용이한 것이다!
코드 중 일부분
아래는 작성한 코드 중 일부분을 가져온 예시이다. 최상단의 Route는 LoginView이고 그안에 Branch가 여러개 있고 Branch안에 각각의 경로가 속해있다는 것
전체 코드는 깃허브에서 확인이 가능 합니다.
final GlobalKey<NavigatorState> _rootNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'root');
final GlobalKey<NavigatorState> _homeTabNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'homeTab');
**/* 생략 */**
final GoRouter router = GoRouter(
navigatorKey: _rootNavigatorKey,
routes: <RouteBase>[
GoRoute(
path: '/',
builder: (context, state) => LoginView(),
),
StatefulShellRoute.indexedStack(
parentNavigatorKey: _rootNavigatorKey,
builder: (context, state, navigationShell) {
return MainView(navigationShell: navigationShell);
},
branches: [
StatefulShellBranch(
navigatorKey: _homeTabNavigatorKey,
routes: [
GoRoute(
path: '/home',
builder: (BuildContext context, GoRouterState state) {
return HomeView();
},
routes: <RouteBase>[
GoRoute(
path: 'notice',
builder: (context, state) => NoticeView(),
routes: <RouteBase>[
GoRoute(
name: 'notice_detail',
path: 'detail/:id',
builder: (context, state) => NoticeDetailView(
notice: noticeVM
.getNoticeById(state.pathParameters['id']!)),
),
],
),
],
),
],
),
**/* 생략 */**
Navigation 연결 & DeepLink
StatefulShellBranch home을 하나의 예시로 가져왔습니다. 아래 코드를 보면 home, notice, detail이 Route안에 있습니다. HomeView안에 있는 NoticeCarouselSection은 NoticeView를 갔다 NoticeDetailView를 가게 됩니다.
사용자 입자에서는 한번에 detail로 가게되지만 뒤로가게되면 notice list와 home이 있습니다.
추후 DeepLink를 연결해주면 될 것 같습니다.
onPressed: () {
context.go('/home/notice/detail/${notice.id}');
},
이렇게 URL Path로 구현하게 되어 Navigation을 원하는 기획대로 구현하였습니다.
아래 화면을 보시면 이해가 쉽습니다.
StatefulShellBranch(
navigatorKey: _homeTabNavigatorKey,
routes: [
GoRoute(
path: '/home',
builder: (BuildContext context, GoRouterState state) {
return HomeView();
},
routes: <RouteBase>[
GoRoute(
path: 'notice',
builder: (context, state) => NoticeView(),
routes: <RouteBase>[
GoRoute(
name: 'notice_detail',
path: 'detail/:id',
builder: (context, state) => NoticeDetailView(
notice: noticeVM
.getNoticeById(state.pathParameters['id']!)),
),
],
),
],
),
],
),
'Flutter' 카테고리의 다른 글
[Flutter] [Firbase] 간편 로그인 구현하기(kakao💬) (1) | 2024.12.19 |
---|---|
[Flutter] DesignSystem 트러블슈팅 (0) | 2024.12.11 |
[Flutter] DynamicTabBar 트러블 슈팅 (1) | 2024.11.27 |
[Flutter] Dynamic TabBar on ScrollView & NaverMap URL Scheme 사용 방법 (0) | 2024.11.22 |
[Flutter] WhiteScreen 해결(iPhone 무선빌드) & Load Sequence Flutter UI (3) | 2024.11.21 |