플러터 Go 라우터 내용 정리

플러터 Go 라우터 내용 정리
Cozy CodingPosted On Jun 19, 202410 min read

FlutterGoRouter

Go_router은 Flutter에서 라우팅을 위한 서드 파티 패키지로, 기본 플러터 라우팅 옵션보다 더 유연하고 사용하기 쉬운 솔루션을 제공하기 위해 만들어졌습니다. 라우트가 어떻게 정의되고 관리되는지에 대해 더 많은 제어를 원하는 경우 유용할 수 있습니다. 또한 웹에 대한 좋은 지원을 제공하기 때문에 애플리케이션에 좋은 선택일 수 있습니다.

URL 패턴을 정의하고, URL을 사용하여 탐색하며, 딥 링크를 처리하고, 기타 여러 네비게이션 관련 시나리오를 다룰 수 있습니다.

특징

고 라우터(GoRouter)는 네비게이션을 간단하게 만들기 위한 다양한 기능을 제공합니다:

  • Navigator API와의 하위 호환성
  • Material 및 Cupertino 앱을 지원
  • StatefulShellRoute를 사용하여 중첩된 탭 탐색 지원
  • 목적지에 대한 여러 화면 표시 (하위 라우트)
  • 템플릿 구문을 사용하여 경로 및 쿼리 매개변수 구문 분석
  • 리디렉션 지원 — 사용자를 다른 URL로 리디렉션할 수 있으며,예를 들어 사용자가 인증되지 않은 경우에는 로그인 페이지로 이동할 수 있습니다.

시작하기

시작하려면 pubspec.yaml에 go_router를 추가하세요. 이 문서에서는 ^13.2.0을 사용하겠습니다.

dependencies:
  go_router: ^13.2.0

라우트 구성

이제 그렇게 한 다음에 GoRouter 구성을 앱에 추가해 봅시다:

import 'package:go_router/go_router.dart';

// GoRouter 구성
final _router = GoRouter(
  initialLocation: '/',
  routes: [
    GoRoute(
      name: 'home', // 선택사항, 라우트에 이름 추가. 경로 대신 이름으로 이동 가능
      path: '/',
      builder: (context, state) => HomeScreen(),
    ),
    GoRoute(
      name: 'shope',
      path: '/shope',
      builder: (context, state) => ShopeScreen(),
    ),
  ],
);

그럼 MaterialApp.router 또는 CupertinoApp.router 생성자를 사용하고 routerConfig 매개변수를 GoRouter 구성 객체로 설정할 수 있어요:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: _router,
    );
  }
}

이것으로 준비 끝이에요 🙂 go_router를 사용해보세요 !!!

매개변수

경로 매개변수를 지정하려면 경로 세그먼트 앞에 콜론(:) 문자를 붙이고 고유한 이름(:userId와 같은)을 따라야 합니다. 빌더 콜백에 제공된 GoRouterState 객체를 통해 매개변수 값을 액세스할 수 있습니다:

GoRoute(
  path: '/fruits/:id',
  builder: (context, state) {
     final id = state.pathParameters["id"]! // URL에서 "id" 매개변수 가져오기
     return FruitsPage(id: id);
  },
),

하위 루트 추가

일치하는 경로는 네비게이터에서 여러 화면이 표시되는 결과를 가져올 수 있습니다. 이는 push()를 호출한 것과 동일한 효과이며, 새 화면이 이전 화면 위에 표시되고 AppBar 위젯에 인앱 뒤로 가기 버튼이 제공됩니다.

다음과 같이 부모 라우트와 그 자식 라우트를 추가해 보세요:

GoRoute(
  path: '/fruits',
  builder: (context, state) {
    return FruitsPage();
  },
  routes: <RouteBase>[ // 자식 라우트 추가
    GoRoute(
      path: 'fruits-details', // 참고: 라우터의 부모에 "/" 문자를 명시할 필요가 없습니다.
      builder: (context, state) {
        return FruitDetailsPage();
      },
    ),
  ],
)

화면 간 이동

go_router를 사용하여 목적지 간에 이동하는 다양한 방법이 있습니다.

새 화면으로 이동하려면 URL과 함께 context.go()를 호출하십시오:

build(BuildContext context) {
  return TextButton(
    onPressed: () => context.go('/fruits/fruit-detail'),
  );
}

URL 대신 이름을 사용하여 탐색할 수도 있습니다. context.goNamed()를 호출하면 됩니다.

build(BuildContext context) {
  return TextButton(
    // 라우트에 "name"을 추가하지 않으면 오류가 발생할 수 있습니다
    onPressed: () => context.goNamed('fruit-detail'),
  );
}

URI에 경로 매개변수를 포함하여 만들려면 Uri 클래스를 사용할 수 있어요:

context.go(
  Uri(
    path: '/fruit-detail',
    pathParameters: {'id': '10'},
  ).toString(),
);

context.pop()을 사용하면 현재 화면에서 뒤로 이동할 수 있어요.

중첩된 탭 탐색

일부 앱은 화면의 하위 섹션에 목적지를 표시합니다. 예를 들어, 화면 간을 이동할 때 항상 화면 상에 남아 있는 BottomNavigationBar와 같은 경우입니다.

StatefulShellRoute를 사용하여 중첩된 탐색을 설정했습니다.

이 StatefulShellRoute 클래스는 루트 네비게이터와 다른 네비게이터에 하위 루트를 배치합니다. 그러나 이 루트 클래스는 각 중첩 분기에 대해 별도의 네비게이터를 생성하므로 (즉, 병렬 탐색 트리), 상태가 있는 중첩된 네비게이션을 구축할 수 있습니다.

예를 들어 BottomNavigationBar를 구현하는 경우, 각 탭에 대한 지속적인 네비게이션 상태가 있는 UI를 구현하는 것이 편리합니다.

상태 유지 쉘 라우트는 상태 유지 브랜치를 나타내는 각각의 StatefulShellBranch 항목 목록을 지정하여 생성됩니다. StatefulShellBranch는 브랜치를 위한 루트 라우트와 네비게이터 키 (GlobalKey) 및 선택적 초기 위치를 제공합니다.

구현 방법을 살펴보겠습니다 🙂

먼저 라우터를 만들어 시작합니다. StatefulShellRoute.indexedStack()을 라우트에 추가할 것이며, 이 클래스는 중첩된 내비게이션을 생성하는 역할을 맡게 됩니다.

StatefulShellRoute.indexedStack()은 중첩된 네비게이터에 IndexedStack을 사용하는 StatefulShellRoute를 생성합니다.

이 생성자는 분기 네비게이터를 나타내는 위젯을 관리하는 컨테이너(navigatorContainerBuilder)에 대한 IndexedStack 기반 구현을 제공합니다.

// `root` 및 `section` 네비게이터를 위한 키 생성하여 불필요한 재구성을 피합니다
final _rootNavigatorKey = GlobalKey<NavigatorState>();
final _sectionNavigatorKey = GlobalKey<NavigatorState>();


final router = GoRouter(
  navigatorKey: _rootNavigatorKey,
  initialLocation: '/home',
  routes: <RouteBase>[
    StatefulShellRoute.indexedStack(
      builder: (context, state, navigationShell) {
        // 커스텀 쉘(예: BottomNavigationBar)을 구현하는 위젯을 반환합니다.
        // 다른 분기로 상태를 유지하면서 이동할 수 있도록 [StatefulNavigationShell]이 전달됩니다.
        return ScaffoldWithNavbar(navigationShell);
      },
      branches: [
        // 1번 탭을 위한 루트 분기
        StatefulShellBranch(
          navigatorKey: _sectionNavigatorKey,
          // 이 분기의 루트 추가
          // 가능한 경우 하위 루트가 있는 각 루트(예: feed/uuid/details)를 추가합니다
          routes: <RouteBase>[
            GoRoute(
              path: '/shope',
              builder: (context, state) => const ShopePage(),
              routes: <RouteBase>[
                GoRoute(
                  path: 'detail',
                  builder: (context, state) => const FeedDetailsPage(),
                )
              ],
            ),
          ],
        ),

        // 2번 탭을 위한 루트 분기
        StatefulShellBranch(routes: <RouteBase>[
          // 이 분기의 루트 추가
          // 가능한 경우 하위 루트가 있는 각 루트(예: shope/uuid/details)를 추가합니다
          GoRoute(
            path: '/home',
            builder: (context, state) => const HomePage(),
          ),
        ])
      ],
    ),
  ],
);

우리의 루트에 StatefulShellRoute.indexedStack()를 추가했습니다. 이것은 우리의 분기를 생성하고 사용자 정의 쉘(이 경우 BottomNavigationBar)을 반환합니다.

  • 빌더: (context, state, navigationShell)에서는 사용자 정의 쉘인 Scaffold with BottomNavigationBar를 반환하며, 이 페이지로 이동할 때 navigationShell을 전달해야 합니다(예: Shope == Home).
  • branches:[]에서는 StatefulShellBranch(분기) 목록을 제공합니다. 이전에 생성한 _sectionNavigatorKey를 첫 번째 분기에 navigatorKey 속성으로 전달하지만, 다른 분기에는 기본 키가 사용됩니다. 또한 해당 분기에 대한 지원 루트 목록인 RouteBase 목록을 제공합니다.

우리의 빌더가 커스텀 쉘을 반환하는 것을 확인할 수 있습니다. 이 쉘에는 BottomNavigationBar가 포함되어 있어요. 그러니 이제 그것을 만들어 봅시다. 👇🏿

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

class ScaffoldWithNavbar extends StatelessWidget {
  const ScaffoldWithNavbar(this.navigationShell, {super.key});

  /// 브랜치 네비게이터를 위한 네비게이션 쉘 및 컨테이너입니다.
  final StatefulNavigationShell navigationShell;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: navigationShell,
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: navigationShell.currentIndex,
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.shop), label: 'Shope'),
          BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
        ],
        onTap: _onTap,
      ),
    );
  }

  void _onTap(index) {
    navigationShell.goBranch(
      index,
      // 바텀 네비게이션 바를 사용할 때 일반적인 패턴은 현재 활성화된 아이템을 탭했을 때 초기 위치로 이동하는 것을 지원하는 것입니다. 이 예제에서는 goBranch의 initialLocation 매개변수를 사용하여 이 동작을 지원하는 방법을 보여줍니다.
      initialLocation: index == navigationShell.currentIndex,
    );
  }
}

기본적으로 BottomNavigationBar가 있는 Scaffold를 반환하며, 본문은 라우터에서 얻은 navigationShell이 될 것입니다.

또한 _onTap(index)가 있습니다. 여기서는 navigationShell.goBranch(index)를 사용하여 브랜치 간에 전환할 수 있습니다.

그럼 이제 여러분의 프로젝트에 이를 구현할 준비가 끝났어요 🥳🎉

자세한 예시는 아래의 저장소를 확인해주세요 👇🏿

Guards

특정 경로를 보호하기 위해, 예를 들어 인증되지 않은 사용자로부터, GoRouter를 통해 전역 리디렉션이 설정될 수 있습니다. 가장 일반적인 예는 /login이 아닌 모든 경로를 보호하는 리디렉트 설정이며, 사용자가 인증되지 않은 경우 /login으로 리디렉트됩니다.

리다이렉션은 GoRouterRedirect 유형의 콜백입니다. 일부 응용 프로그램 상태에 따라서 들어오는 위치를 변경하려면 GoRouter 또는 GoRoute 생성자에 콜백을 추가하세요:

GoRouter(
  redirect: (BuildContext context, GoRouterState state) {
    final isAuthenticated = // 사용자가 인증되었는지 확인하는 논리를 여기에 작성하세요
    if (!isAuthenticated) {
      return '/login';
    } else {
      return null; // 리디렉트 없이 의도한 경로를 표시하려면 "null"을 반환하세요
    }
  },
  ...
  • GoRouter 생성자에서 리다이렉트를 정의할 수 있습니다. 모든 탐색 이벤트보다 먼저 호출됩니다.
  • GoRoute 생성자에서 리다이렉트를 정의할 수 있습니다. 탐색 이벤트가 경로를 표시하기 직전에 호출됩니다.

전환 애니메이션

GoRouter를 사용하면 각 GoRoute에 대한 전환 애니메이션을 사용자 정의할 수 있어요. 사용자 정의 전환 애니메이션을 구성하려면 GoRoute 생성자에 pageBuilder 매개변수를 제공하세요:

GoRoute(
  path: '/fruit-details',
  pageBuilder: (context, state) {
    return CustomTransitionPage(
      key: state.pageKey,
      child: FruitDetailsScreen(),
      transitionsBuilder: (context, animation, secondaryAnimation, child) {
        // 애니메이션의 값에 따라 화면의 불투명도를 변경합니다.
        return FadeTransition(
          opacity: CurveTween(curve: Curves.easeInOutCirc).animate(animation),
          child: child,
        );
      },
    );
  },
),

전체 예제는 전환 애니메이션 샘플을 참조하세요.

에러 처리 (404 페이지)

기본적으로, go_router는 MaterialApp 및 CupertinoApp용 기본 오류 화면과 사용되지 않는 경우의 기본 오류 화면이 함께 제공됩니다. 또한 errorBuilder 매개변수를 사용하여 기본 오류 화면을 대체할 수도 있습니다:

GoRouter(
  /* ... */
  errorBuilder: (context, state) => ErrorPage(state.error),
);

이전에 이동하기 전에 !!

go_router에는 아직도 좋은 기능이 있습니다. GoRouter에 NavigatorObserver를 추가하여 Navigator의 동작을 관찰하고 route가 푸시, 팝 또는 대체될 때마다 알림을 받습니다. 이를 위해 NavigatorObserver를 확장하는 클래스를 만들어 보겠습니다:

class MyNavigatorObserver extends NavigatorObserver {
  @override
  void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
    log('did push route');
  }

  @override
  void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
    log('did pop route');
  }

이제 MyNavigatorObserver를 GoRouter에 추가해 봅시다.

GoRouter(
  ...
  observers: [ // 내비게이터 옵저버 추가
    MyNavigatorObserver(),
  ],
...
)

이벤트가 발생하면 내비게이터에 알림이 전달됩니다.