현대적인 Flutter 애플리케이션 개발에서 네비게이션 관리는 단순한 화면 전환을 넘어 사용자 경험과 시스템 안정성을 결정짓는 핵심 요소입니다. 기존에 사용되던 정적 문자열 기반 경로 지정 방식은 런타임 에러 발생 가능성이 높고, 리팩토링 시 파라미터 누락 등으로 인해 유지보수 부담이 컸습니다. 이러한 문제를 해결하기 위해 auto_route 라이브러리는 코드 생성(CODE Generation) 기술을 활용하여 컴파일 단계에서 경로의 유효성을 검증할 수 있는 강력한 대안을 제시합니다.
환경 설정 및 의존성 관리
프로젝트에 auto_route를 도입하려면 먼저 pubspec.yaml 파일에 필요한 패키지 정보를 명시해야 합니다. 일반적인 의존성과 빌드 도구를 분리하여 관리하는 것이 권장됩니다.
dependencies:
flutter:
sdk: flutter
auto_route: ^7.8.0
dev_dependencies:
build_runner: ^2.4.6
auto_route_generator: ^7.8.0
설정 완료 후 명령어 터미널에서 flutter pub get을 실행하여 패킷을 다운로드받은 뒤, 반드시 다음 명령어를 통해 코드 생성기를 구동해야 합니다.
dart run build_runner build --delete-conflicting-extensions
라우터 구조 정의 및 타입 매핑
핵심 작업은 모든 화면 흐름을 정의하는 라우터 클래스를 생성하는 것입니다. 이 과정에서 @MaterialAutoRouter 어노테이션을 사용하여 생성될 클래스의 베이스와 연결하려는 화면을 지정합니다. 예시에서는 관리자용 대시보드와 개별 상품 뷰를 연결하는 구조를 가정했습니다.
import 'package:auto_route/auto_route.dart';
import 'package:my_app/ui/dashboards/overview_screen.dart';
import 'package:my_app/ui/items/item_detail_view.dart';
part 'app_navigation.gr.dart';
@MaterialAutoRouter(
replaceInRouteName: 'Screen,Route',
routes: [
MaterialRoute(page: OverviewScreen, initial: true),
MaterialRoute(
page: ItemDetailView,
path: '/item/:product_code',
),
],
)
class AppNavigation extends _$AppNavigation {}
이 설정에 따라 빌드가 수행되면 app_navigation.gr.dart 파일이 자동 생성되며, 여기서 정의된 라우트 타입들을 안전하게 호출할 수 있게 됩니다.
파라미터 주입 및 전달 방식
화면 간 데이터를 넘기는 방식은 크게 URL 경로 내에 포함되는 @pathParam과 별도 인자로 전달되는 @RoutePage 매개변수로 구분됩니다. 아래 예제는 특정 제품의 상세 정보를 보기 위해 코드를 경로로 받는 경우와 추가 필터 옵션을 동적으로 넘기는 방식을 보여줍니다.
@RoutePage()
class ItemDetailView extends StatelessWidget {
const ItemDetailView({
super.key,
@PathParam(String) required this.productCode,
@QueryParam(String?) this.sortOption,
});
final String productCode;
final String? sortOption;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('제품: $productCode')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('코드: $productCode'),
if (sortOption != null) Text('옵션: $sortOption'),
],
),
),
);
}
}
이러한 정의가 이루어지면, 탐색 로직 작성 시에는 단순히 인스턴스를 생성하여 넘기기만 하면 됩니다. 타입 체커가 잘못된 값을 방지하므로 런타임 오류가 근본적으로 줄어듭니다.
// 경로 파라미터를 포함한 이동
_mainRouter.navigate(ItemDetailViewRoute(productCode: 'A1020'));
// 쿼리 스트링을 함께 사용하는 경우
context.router.push(
ItemDetailViewRoute(productCode: 'B2023').withQueryParameters(sortOption: 'price_asc'),
);
중첩된 스택 관리 및 스코프 제어
복잡한 UI 구조를 가진 탭 바나 서브 페이지를 다룰 때는 중첩된 라우팅 계층이 필요합니다. children 속성을 활용해 부모 컨테이너 내부의 독립된 네비게이션 스택을 구성할 수 있습니다.
@MaterialAutoRouter(
routes: [
MaterialRoute(
page: MainContainerScreen,
children: [
MaterialRoute(page: UserProfileScreen),
MaterialRoute(page: OrderHistoryScreen),
MaterialRoute(page: AccountSettingScreen),
],
),
],
)
이때 각 하위 라우트는 고유한 스택을 가지며 필요 시 상위 스택으로 복귀하거나 다른 브랜치로 분기할 수 있습니다. 현재 실행 중인 컨텍스트에 따라 올바른 라우트를 참조하기 위해서는 적절한 범위(Scope)를 인지하는 것이 중요합니다.
// 상위 네비게이션 컨트롤러 접근
final parentNavigator = AutoRouter.of(context).parent();
// 루트 레벨의 전역 라우터 참조
final rootNavigator = AutoRouter.of(context).root();
// 현재 열린 라우트의 상태 확인
if (context.canPop()) {
context.pop();
}
접근 제어를 위한 인터셉터 구현
특정 페이지로 진입할 때 권한 체크나 데이터 초기화가 필요한 경우가 많습니다. 이를 위해 AutoRouteGuard를 상속받아 커스텀 검증을 수행하는 패턴을 사용합니다. 이는 로그인 여부 확인 외에도 광고 노출 로직 등 다양한 비즈니스 규칙 적용에 유용합니다.
class UserPermissionChecker implements AutoRouteGuard {
@override
void onNavigation(NavigationResolver resolver, StackRouter router) async {
// 비동기 인증 검사
final isValid = await AuthService.checkUserActive();
if (isValid) {
// 정상 진행
resolver.next(true);
} else {
// 차단 및 알림 또는 리디렉션
resolver.next(false);
// 혹은 강제 로그아웃 처리
router.replaceAll([
AuthSignInRoute(),
]);
}
}
}
생성한 인터셉터를 라우트 정의에 명시하면 해당 경로로의 모든 요청이 자동으로 검사 절차를 거치게 됩니다.
routes: [
MaterialRoute(
page: AdminDashboard,
guards: [UserPermissionChecker()],
),
]