Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 |
Tags
- Algorithm
- Flutter Netwrok Logging
- 멀티모듈 고 라우터
- Swift
- Di
- 네트워크
- Flutter Network Interceptor
- Jira Slack 연동
- 멀티패키지
- Bitbucket Slack 연동
- Python
- 고 라우터
- 난수
- 플러터
- 단편화
- IP
- 알고리즘
- 인터셉터 구현
- ci/cd
- Match
- ios
- 스위프트
- flutter interceptor
- 펍 워크스페이스
- go_router
- 플러터 인터셉터
- Flutter
- pub workspace go_router
- Flutter Logging
- 패킷
Archives
- Today
- Total
INEEDACHACHA
Pub Workspace에서 go_router 기반 네비게이션 시스템 구현 본문
개요
- 현재 전환 중인 앱에서 Clean Architecture를 유지하면서 go_router를 사용한 네비게이션 시스템을 구현.
- 멀티 패키지 구조에서 상호참조 없이 안전한 네비게이션을 제공한다.
스토리
- 막상 Bottom Navigation Bar 구현 후, 이 것을 어떻게 멀티 패키지에서 쓰지? 하는 고민이 뒤늦게 생겼다.
- Pub Workspace에서 go_router를 어떻게 쓰는지 예시가 나온 블로그는 없었던 것 같아서 글을 남긴다.
아키텍처 구조
의존성 방향
Apps Layer (****)
├── AppRouter (go_router 설정)
├── NavigationServiceImpl (구현체)
└── DI Container 등록
↓ (DI를 통한 주입)
Feature Layer (home_feature, feed_feature, etc.)
├── NavigationService 인터페이스 사용
└── GetIt.instance<NavigationService>()로 접근
Shared Layer (navigation)
└── NavigationService 추상 인터페이스 정의파일 구조
📁 apps/****/
├── lib/router/
│ ├── app_router.dart # GoRouter 설정
│ └── app_route_path.dart # 라우트 경로 정의
├── lib/services/
│ └── navigation_service_impl.dart # NavigationService 구현체
└── lib/di/
└── app_di.dart # DI 설정
📁 packages/shared/navigation/
└── lib/navigation_service.dart # 추상 인터페이스
📁 packages/feature/home_feature/
└── lib/home_page.dart # NavigationService 사용주요 구현 코드
1. NavigationService 인터페이스
// packages/shared/navigation/lib/navigation_service.dart
abstract interface class NavigationService {
void goBack();
void goToHome();
void goToHealthFeed();
void goToRecord();
void goToMyPage();
void goToHomeDetail();
}2. 라우트 경로 정의
// apps/****/lib/router/app_route_path.dart
enum RoutePaths {
home('/home'),
healthFeed('/health-feed'),
record('/record'),
myPage('/my-page'),
homeDetail('detail');
final String path;
const RoutePaths(this.path);
}3. AppRouter 구현
// apps/****/lib/router/app_router.dart
@singleton
class AppRouter {
static GoRouter? _router;
GoRouter get router {
_router ??= GoRouter(
initialLocation: RoutePaths.home.path,
routes: [
StatefulShellRoute.indexedStack(
builder: (context, state, shell) => MainTabView(
currentIndex: shell.currentIndex,
onTap: (int index) => shell.goBranch(index),
child: shell,
),
branches: [
StatefulShellBranch(
routes: [
GoRoute(
path: RoutePaths.home.path,
name: RoutePaths.home.name,
builder: (context, state) => const HomePage(),
routes: [
GoRoute(
path: RoutePaths.homeDetail.path,
name: RoutePaths.homeDetail.name,
builder: (context, state) => const HomeDetailPage(),
),
],
),
],
),
// mypage, record, feed 탭들
],
),
],
);
return _router!;
}
}4. NavigationService 구현체
// apps/****/lib/services/navigation_service_impl.dart
@Singleton(as: NavigationService)
class NavigationServiceImpl implements NavigationService {
final AppRouter _appRouter;
NavigationServiceImpl(this._appRouter);
GoRouter get _router => _appRouter.router;
@override
void goToHomeDetail() {
_router.goNamed(RoutePaths.homeDetail.name);
}
@override
void goToHome() {
_router.go(RoutePaths.home.path);
}
}5. Feature에서의 사용
// packages/feature/home_feature/lib/home_page.dart
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: ElevatedButton(
onPressed: () {
GetIt.instance<NavigationService>().goToHomeDetail();
},
child: Text('Go to Detail'),
),
);
}
}주요 특징
장점
- 상호참조 방지: Feature 패키지들이 Apps를 직접 참조하지 않음
- Clean Architecture 준수: 의존성 역전 원칙 적용
- Injectable 활용: 일관된 DI 패턴 사용
- 타입 안전성: 컴파일 타임에 의존성 검증
- 테스트 용이성: Mock 객체로 쉽게 교체 가능
- 라우터 랩핑: 라우터를 구현체로 랩핑해 Router를 구체적으로 모르게 만듦
핵심 원리
- Service Locator 패턴: GetIt을 통한 전역 서비스 접근
- Dependency Inversion: Feature → Interface ← Implementation
- StatefulShellRoute: 탭 상태 유지하면서 네비게이션
설정 방법
1. 의존성 추가
# pubspec.yaml (root)
workspace:
- packages/shared/navigation # 추가 필요
# apps/****/pubspec.yaml
dependencies:
go_router: ^16.0.0
navigation:2. DI 설정
// apps/****/lib/main.dart
void runAppWithFlavor(Flavor flavor) async {
WidgetsFlutterBinding.ensureInitialized();
await initEnv(flavor);
await configureDependencies(); // NavigationService 등록
runApp(const MyApp());
}3. 코드 생성
flutter packages pub run build_runner build --delete-conflicting-outputs주의사항
- 하위 라우트 경로: 상대 경로로 설정 (
'detail'not'/home/detail') - Workspace 등록: 루트 pubspec.yaml에 navigation 패키지 추가 필요
- Build Runner: DI 설정 변경 시 반드시 실행
- Named Route: enum의 기본
name속성 활용
확장 방법
새로운 라우트 추가
RoutePathsenum에 경로 추가AppRouter에 GoRoute 설정NavigationService에 메서드 추가NavigationServiceImpl에 구현- Build runner 실행
예시: 설정 페이지 추가
// 1. 경로 추가
enum RoutePaths {
// 기존 경로들...
myPageSettings('settings');
}
// 2. 라우트 설정
GoRoute(
path: RoutePaths.myPageSettings.path,
name: RoutePaths.myPageSettings.name,
builder: (context, state) => const MyPageSettingsPage(),
),
// 3. 인터페이스 추가
abstract interface class NavigationService {
void goToMyPageSettings();
}
// 4. 구현 추가
@override
void goToMyPageSettings() {
_router.goNamed(RoutePaths.myPageSettings.name);
}결과
- 멀티 패키지 환경에서 안전한 네비게이션 구현
- Clean Architecture 원칙 준수
- 상호참조 없는 의존성 구조
- 확장 가능한 네비게이션 시스템
'Flutter > 전환실록' 카테고리의 다른 글
| Flutter Project Atlassian Setting (0) | 2025.07.23 |
|---|---|
| Flutter Network Logging Interceptor (4) | 2025.07.22 |
| Flutter Pub Workspaces 구조 – 현재 프로젝트 기준 (1) | 2025.07.15 |
| Flutter Cocoapods -> SPM 전환 Trouble Shooting (6) | 2025.07.10 |
| Flutter Network Module => Swift Moya Style로 만들어보기 (0) | 2025.06.13 |