본문 바로가기

Flutter

Flutter GoRouter로 페이지 관리하기

go_router 패키지는 구글 플러터 공식 패키지로 라우팅 관련한 기능들을 사용하기 쉽게 도와주는 패키지이다.

https://docs.page/csells/go_router

 

go_router

 

docs.page

 

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