go_router 패키지는 구글 플러터 공식 패키지로 라우팅 관련한 기능들을 사용하기 쉽게 도와주는 패키지이다.
https://docs.page/csells/go_router
Setting
pubspec.yaml
go_router: ^6.3.0
main.dart
void main() {
runApp(const _App());
}
class _App extends StatelessWidget {
const _App({Key? key}) : super(key: key);
final GoRouter _router =
GoRouter(
initialLocation: "/",
routes: [
GoRoute(
path: "/",
builder: (_, state) => HomeScreen(),
routes: [
GoRoute(
path: "one",
builder: (_, state) => OneScreen()
)
]
),
],
);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routeInformationProvider: _router.routeInformationProvider,
routeInformationParser: _router.routeInformationParser,
routerDelegate: _router.routerDelegate,
);
}
}
- GoRouter를 반환해주는 get 함수를 선언해준다. GoRouter의 initialLocation은 디폴트 페이지의 uri String을, routes 속성에는 프로젝트에서 사용되는 페이지를 GoRoute 클래스로 정의한다. GoRoute의 path에는 페이지 uri String을, builder에는 해당 페이지를 반환하는 함수를 작성한다.
- GoRouter를 사용하려면 MaterialApp() 대신 MaterialApp.router() 형태로 사용해야한다.
- routeInformaterProvider : 라우트 상태를 전달해주는 함수
- routeInformationParser: URI String을 상태 및 GoRouter에서 사용할 수 있는 형태로 변환해주는 함수
- routerDelegate: routeInformationParser에서 변환된 값을 어떤 라우트로 보여줄 지 정하는 함수
- nesting: GoRoute의 routes 속성을 통해서도 라우트를 선언할 수 있는데 이를 nesting이라고 한다. nesting으로 선언된 GoRoute에서는 path에 /를 기입하지 않는다.
.go()
context.go("/one");
파라미터로 제공받은 path로 이동한다. push()와 go()의 차이점은 go()는 하위 페이지를 모두 푸시하고 push()는 해당 페이지만 푸시한다.
Router 정보 받아오기
final _router = GoRouter.of(context);
위의 코드로 현재 라우터를 받아올 수 있다.
.goNamed()
GoRoute(
path: "three",
name: ThreeScreen.routeName,
builder: (_, state) => ThreeScreen(),
)
위처럼 name 속성에 페이지 route name을 전달하고
context.goNamed(ThreeScreen.routeName);
goNamed()에 파라미터로 route name을 전달하면 해당 페이지로 이동한다.
.pop()
context.pop();
뒤로가기 기능, Navigator.pop(context)와 같은 기능을 한다.
errorBuilder
routing 간에 오류가 발생했을 때 일괄적으로 처리할 수 있는 방법이 있는데 GoRouter에 errorBuilder를 정의해주면 된다.
final GoRouter _router = GoRouter(
initialLocation: "/",
errorBuilder: (context, state) {
return ErrorScreen(error: state.error.toString());
},
...
없는 path로 이동을 시도하는 등의 액션이 발생하면 errorBuilder에서 반환하는 페이지로 이동하게 된다.
Redirection, Refresh
개발을 하다보면 로그인 상태나 토큰 만료 등 redirection이 필요한 상황에서의 처리가 필요한데 이는 GoRouter를 통해 쉽게 해결할 수 있다. 상태는 riverpod을 이용해서 관리해보았다.
user_model.dart
class UserModel {
final String name;
UserModel({
required this.name,
});
}
유저 로그인/로그아웃 상태를 판단할 때 사용할 UserModel 샘플
router_provider.dart
final routerProvider = Provider<GoRouter>((ref) {
// authProvider의 상태가 변경될 때 마다 redirection
final authProvider = _AuthNotifier(ref: ref);
return GoRouter(
initialLocation: "/login",
errorBuilder: (context, state) {
return ErrorScreen(error: state.error.toString());
},
routes: authProvider._routes,
redirect: authProvider._redirectLogic, // navigate 될 때마다 호출
refreshListenable: authProvider, // authProvider 상태를 listen하여 상태가 변하면 redirect 실행
);
});
class _AuthNotifier extends ChangeNotifier {
final Ref ref;
_AuthNotifier({required this.ref}) {
// UserModel의 상태 listen
// 상태가 변경되면 notifyListeners() 호출
ref.listen<UserModel?>(
userProvider,
(previous, next) {
if (previous != next) {
notifyListeners();
}
},
);
}
Future<String?> _redirectLogic(_, GoRouterState state) async {
final user = ref.read(userProvider);
const loginPath = "/login";
final loggingIn = state.location == loginPath;
if (user == null) {
return loggingIn ? null : loginPath;
}
if (loggingIn) return "/";
return null;
}
List<GoRoute> get _routes => [
GoRoute(
path: "/",
builder: (_, state) => HomeScreen(),
routes: [
GoRoute(
path: "one",
builder: (_, state) => OneScreen(),
routes: [
GoRoute(
path: "two",
builder: (_, state) => TwoScreen(),
routes: [
GoRoute(
path: "three",
name: ThreeScreen.routeName,
builder: (_, state) => ThreeScreen(),
)
],
)
],
),
],
),
GoRoute(
path: "/login",
builder: (_, state) => LoginScreen(),
),
];
}
final userProvider = StateNotifierProvider<UserStateNotifier, UserModel?>(
(ref) => UserStateNotifier());
class UserStateNotifier extends StateNotifier<UserModel?> {
UserStateNotifier() : super(null);
void login({required String name}) {
state = UserModel(name: name);
}
void logout() {
state = null;
}
}
main.dart 함수에 선언했던 GoRouter를 routerProvider로 관리한다.
Flutter에서 기본적으로 제공하는 ChangeNotifer를 상속받는 AuthNotifier class에 실제 리다이렉션 메서드를 정의하고 GoRouter의 redirect 속성에 이 함수를 넣어준다. redirection 함수는 navigate 될 때 마다 실행되는데, 페이지 내에서 토큰 만료 등과 같이 상태가 변경되었을 때에도 redirection 함수가 실행되게 하려면 refreshListenable에 authProvider를 넣어주면된다.
main.dart
void main() {
runApp(
ProviderScope(
child: _App(),
),
);
}
class _App extends ConsumerWidget {
const _App({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final router = ref.watch(routerProvider);
return MaterialApp.router(
routeInformationProvider: router.routeInformationProvider,
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate,
);
}
}
지금까지 Navigator를 이용하여 페이지 관리를 해왔는데, 사용하면서 지저분(?)한 상황이 많았던 것 같다. GoRouter는 페이지 관리가 비교적 깔끔하고, 특히 redirection을 처리하는데 매우 편리해서 다음 프로젝트부터는 필수 패키지가 될 것 같다.
'Flutter' 카테고리의 다른 글
뒤늦게 정리하는 Flutter riverpod 개념 (0) | 2023.04.06 |
---|---|
Debounce와 Throttle (0) | 2023.04.06 |
Flutter [.mapIndexed()] method (0) | 2023.03.19 |
Flutter Naver Login Android 이슈 (0) | 2023.03.14 |
SingleChildScrollView keyboardDismissBehavior (0) | 2023.02.23 |