Flutter로 간단한 노트 앱을 만들어보자 ③ - GoRouter로 네비게이션 바 리팩터링
이번 글에서는 기존의 인덱스 방식의 화면 전환을 Enum 과 GoRouter 를 사용하여 리팩터링 해보겠습니다.
GoRouter란?
GoRouter는 Flutter의 네비게이션을 간편하게 관리할 수 있도록 도와주는 패키지입니다. URL 기반의 라우팅을 지원하며 화면 전환과 관련된 복잡한 과정을 간단히 처리할 수 있습니다.
리팩터링 이유
기존의 탭 전환 방식에서는 네비게이션을 인덱스로 관리했는데, 이렇게 인덱스로 화면 전환을 관리하면 화면이 추가되거나 순서가 변경될 때 수정이 번거롭습니다. 배열 안에 정의된 화면의 순서를 모두 외우고 있어야 개발이 가능하죠.
그렇기 때문에 이번 글에서는 Enum 과 GoRouter 를 이용하여 각 화면을 명확히 구분하고, 경로 기반의 전환으로 더 명확하고 유지보수하기 쉬운 구조로 변경하겠습니다.
그럼 이제부터 본격적인 리팩터링을 시작해 보겠습니다.!
1. enum
으로 탭 항목 정의
먼저 각 화면 탭을 enum
으로 정의합니다.
// lib/enums.dart
enum TabItem { noteDetail, noteList, settings }
이렇게 enum 으로 정의하면 인덱스 보다 직관적으로 페이지를 호출할 수 있습니다. 가독성뿐만 아니라 개발에서의 실수도 줄일 수 있죠.
2. GoRouter 설정 추가하기
GoRouter 를 사용하기 위해 먼저 패키지를 설치합니다.
flutter pub add go_router
설치가 끝났다면 main.dart 파일을 수정해 보겠습니다.
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'components/bottom_navbar.dart';
import 'views/note_detail_view.dart';
import 'views/note_list_view.dart';
import 'views/settings_view.dart';
import 'enums.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final GoRouter _router = GoRouter( // 수정한 부분
initialLocation: '/noteDetail',
routes: [
ShellRoute(
builder: (context, state, child) {
return HomeScreen(child: child);
},
routes: [
GoRoute(
path: '/noteDetail',
name: TabItem.noteDetail.name,
builder: (context, state) => NoteDetailView(),
),
GoRoute(
path: '/noteList',
name: TabItem.noteList.name,
builder: (context, state) => NoteListView(),
),
GoRoute(
path: '/settings',
name: TabItem.settings.name,
builder: (context, state) => SettingsView(),
),
],
),
],
);
@override
Widget build(BuildContext context) {
return MaterialApp.router( // 수정한 부분
routerConfig: _router,
theme: ThemeData.dark(),
);
}
}
각 화면을 URL 경로와 연결해 주면 화면 간 이동을 관리할 수 있는데요. ShellRoute를 사용해 HomeScreen의 네비게이션을 관리하도록 구성했습니다.
이렇게 만든 _router 를 기존 MaterialApp에 주입하기 위해 MaterialApp.router로 변경하여 넣습니다.
3. BottomNavBar 수정 및 탭 전환 로직 구현
기존 currentIndex 를 이번에 새로 정의한 TabItem 이넘으로 수정하도록 BottomNavBar 를 수정합니다. 주석으로 표시한 `수정한 부분`을 참고해 주세요.
// lib/components/bottom_navbar.dart
import 'package:flutter/material.dart';
import 'package:ttingnote/enums.dart';
class BottomNavBar extends StatelessWidget {
final TabItem currentTab;
final ValueChanged<TabItem> onTabSelected;
const BottomNavBar({ // 수정한 부분
Key? key,
required this.currentTab,
required this.onTabSelected,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return BottomNavigationBar(
backgroundColor: Colors.black,
selectedItemColor: Colors.white,
unselectedItemColor: Colors.grey,
currentIndex: TabItem.values.indexOf(currentTab), // 수정한 부분
onTap: (index) { // 수정한 부분
onTabSelected(TabItem.values[index]);
},
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.create_outlined),
label: '노트 쓰기',
),
BottomNavigationBarItem(
icon: Icon(Icons.list_alt_outlined),
label: '노트 조회',
),
BottomNavigationBarItem(
icon: Icon(Icons.settings_outlined),
label: '설정',
),
],
);
}
}
그리고 HomeScreen 에서는 탭이 선택될 때마다 GoRouter 를 통해 해당 경로로 이동하도록 설정하겠습니다.
// lib/main.dart - HomeScreen 클래스 수정
class HomeScreen extends StatefulWidget {
final Widget child;
const HomeScreen({Key? key, required this.child}) : super(key: key);
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
TabItem _currentTab = TabItem.noteDetail;
void _onTabSelected(TabItem tabItem) { // 수정한 부분
setState(() {
_currentTab = tabItem;
switch (tabItem) {
case TabItem.noteDetail:
context.goNamed(TabItem.noteDetail.name);
break;
case TabItem.noteList:
context.goNamed(TabItem.noteList.name);
break;
case TabItem.settings:
context.goNamed(TabItem.settings.name);
break;
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold( // 수정한 부분
body: widget.child,
bottomNavigationBar: BottomNavBar(
currentTab: _currentTab,
onTabSelected: _onTabSelected,
),
);
}
}
이렇게 TabItem 이넘과 GoRouter 를 사용하여 코드를 리팩터링 해보았습니다.
결과 화면은?
이전과 다르지 않습니다. 이번 수정은 코드를 리팩터링만 한 것이기 때문에 기능적으로는 바뀐 게 없어요. 다만, 리팩터링 한 후에는 기존처럼 잘 동작하는지 확인이 꼭 필요합니다.
여기서 리팩터링을 마칠 수도 있지만,,, 개인적으로 화면 전환이 아쉽더군요. 오른쪽에서 왼쪽으로 전환되는 슬라이드 효과가 아닌 페이드인아웃 효과로 화면 전환을 바꿔보겠습니다.
4. 화면 전환을 페이드 효과로 변경
앞서 GoRouter 를 정의한 main.dart 파일로 다시 돌아갑니다.
우리는 화면 전환을 페이드 효과로 변경할 것이므로 FadeTransition을 활용하겠습니다. 먼저 FadeTransition 를 리턴하는 _fadeTransition 함수를 만들겠습니다.
// lib/main.dart
// 페이드 전환 애니메이션 설정
static Widget _fadeTransition(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return FadeTransition(
opacity: animation,
child: child,
);
}
그리고 상단에 정의된 GoRouter 의 builder 파라미터를 pageBuilder 파라미터로 수정합니다. pageBuilder 를 사용하면 페이지 전환을 커스텀할 수 있습니다.
기존 코드:
GoRoute(
path: '/noteDetail',
name: TabItem.noteDetail.name,
builder: (context, state) => NoteDetailView())
)
수정 코드:
GoRoute(
path: '/noteDetail',
name: TabItem.noteDetail.name,
pageBuilder: (context, state) => CustomTransitionPage(
key: state.pageKey,
child: NoteDetailView(),
transitionDuration: const Duration(milliseconds: 200),
transitionsBuilder: _fadeTransition,
)
)
나머지 /noteList 와 /settings 부분도 동일하게 수정해 줍니다.
코드를 수정했다면 이제 화면 전환이 바뀌었는지 확인합시다.
우리가 의도한 대로 슬라이드에서 페이드로 화면 전환이 바뀐 것을 볼 수 있습니다.
마무리
이번 글에서는 기존의 인덱스 기반 화면 전환을 Enum 과 GoRouter 를 사용해 리팩터링 하며 네비게이션 구조를 더 직관적이고 유지보수하기 쉽게 개선했습니다.
또한, 화면 전환 애니메이션을 슬라이드에서 페이드 효과로 변경해 보았습니다. 화면 전환 효과는 페이드 효과 외에도 다양하게 적용할 수 있으니 필요에 따라 커스텀하여 적용해 보시기 바랍니다.
[Flutter로 간단한 노트 앱을 만들어보자] 시리즈는 직접 독학으로 하나씩 만들어나가는 과정이므로 틀리거나 부족한 내용이 있을 수 있습니다. 조언과 피드백을 댓글로 남겨주시면 적극 반영하겠습니다. 감사합니다.