import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:tiktok_clone/common/widgets/main_navigation/main_navigation_screen.dart';
import 'package:tiktok_clone/features/authentication/repos/authentication_repo.dart';
import 'package:tiktok_clone/features/authentication/views/login_screen.dart';
import 'package:tiktok_clone/features/authentication/views/sign_up_screen.dart';
import 'package:tiktok_clone/features/inbox/views/activity_screen.dart';
import 'package:tiktok_clone/features/inbox/views/chat_detail_screen.dart';
import 'package:tiktok_clone/features/inbox/views/chats_screen.dart';
import 'package:tiktok_clone/features/notifications/notifications_provider.dart';
import 'package:tiktok_clone/features/onboarding/interests_screen.dart';
import 'package:tiktok_clone/features/videos/views/video_recording_screen.dart';
final routerProvider = Provider((ref) {
// ref.watch(authState);
return GoRouter(
initialLocation: "/home",
redirect: (context, state) {
final isLoggedIn = ref.read(authRepo).isLoggedIn;
if (!isLoggedIn) {
if (state.subloc != SignUpScreen.routeURL &&
state.subloc != LoginScreen.routeURL) {
return SignUpScreen.routeURL;
}
}
return null;
},
routes: [
ShellRoute(
builder: (context, state, child) {
ref.read(notificationsProvider(context));
return child;
},
routes: [
GoRoute(
name: SignUpScreen.routeName,
path: SignUpScreen.routeURL,
builder: (context, state) => const SignUpScreen(),
),
GoRoute(
name: LoginScreen.routeName,
path: LoginScreen.routeURL,
builder: (context, state) => const LoginScreen(),
),
GoRoute(
name: InterestsScreen.routeName,
path: InterestsScreen.routeURL,
builder: (context, state) => const InterestsScreen(),
),
GoRoute(
path: "/:tab(home|discover|inbox|profile)",
name: MainNavigationScreen.routeName,
builder: (context, state) {
final tab = state.params["tab"]!;
return MainNavigationScreen(tab: tab);
},
),
GoRoute(
name: ActivityScreen.routeName,
path: ActivityScreen.routeURL,
builder: (context, state) => const ActivityScreen(),
),
GoRoute(
name: ChatsScreen.routeName,
path: ChatsScreen.routeURL,
builder: (context, state) => const ChatsScreen(),
routes: [
GoRoute(
name: ChatDetailScreen.routeName,
path: ChatDetailScreen.routeURL,
builder: (context, state) {
final chatId = state.params["chatId"] ?? "";
return ChatDetailScreen(
chatId: chatId,
);
},
),
],
),
GoRoute(
path: VideoRecordingScreen.routeURL,
name: VideoRecordingScreen.routeName,
pageBuilder: (context, state) => CustomTransitionPage(
transitionDuration: const Duration(milliseconds: 200),
child: const VideoRecordingScreen(),
transitionsBuilder:
(context, animation, secondaryAnimation, child) {
final position = Tween(
begin: const Offset(0, 1),
end: Offset.zero,
).animate(animation);
return SlideTransition(
position: position,
child: child,
);
},
),
),
],
),
],
);
});
이 코드는 GoRouter
를 사용하여 Flutter 앱 내에서 네비게이션을 관리하는 방법을 보여줍니다. GoRouter
는 Flutter의 라우팅과 네비게이션을 위한 선언적, 타입 안전한 방법을 제공하는 패키지입니다. 이를 사용하면 앱의 라우트를 중앙에서 관리할 수 있으며, URL 기반 네비게이션을 쉽게 구현할 수 있습니다. flutter_riverpod
와 함께 사용되어, 앱의 로그인 상태에 따라 초기 라우트나 리디렉션을 처리하는 로직을 포함하고 있습니다.
초기 위치(initialLocation
): 앱이 시작될 때 사용자에게 처음으로 보여줄 라우트를 정의합니다. 여기서는 "/home"
이 초기 위치로 설정되어 있습니다.
리디렉션 로직(redirect
): 사용자의 로그인 상태를 검사하고, 로그인하지 않은 사용자를 SignUpScreen
또는 LoginScreen
으로 리디렉션합니다. 이는 ref.read(authRepo).isLoggedIn
을 통해 확인되며, 현재 위치가 회원가입이나 로그인 화면이 아닐 경우, 회원가입 화면으로 리디렉션합니다.
라우트 정의(routes
): 앱에서 사용할 라우트를 GoRoute
객체의 리스트로 정의합니다. 각 GoRoute
는 라우트의 이름(name
), 경로(path
), 그리고 해당 라우트에 도달했을 때 빌드할 위젯(builder
또는 pageBuilder
)을 지정합니다.
중첩 라우트(ShellRoute
와 내부 routes
): ShellRoute
는 공통의 네비게이션 바나 사이드바 같은 공통 요소를 가진 라우트들을 위한 컨테이너 역할을 합니다. 이 예제에서는 ShellRoute
내부에 여러 GoRoute
를 정의하여, 각기 다른 화면으로의 네비게이션을 구성합니다.
파라미터와 URL 매핑: 일부 라우트에서는 URL로부터 파라미터를 받아 처리합니다. 예를 들어, MainNavigationScreen
라우트는 URL의 /:tab(home|discover|inbox|profile)
부분에서 tab
파라미터를 추출하여, 해당하는 탭으로 사용자를 이동시킵니다.
사용자 정의 페이지 전환(CustomTransitionPage
): VideoRecordingScreen
에 대한 라우트에서는 CustomTransitionPage
를 사용하여 사용자 정의 페이지 전환 효과를 구현합니다. 여기서는 슬라이드 트랜지션을 사용하여 화면 전환 효과를 추가합니다.
GoRouter
는 Flutter에서 선언적 네비게이션을 구현하기 위한 패키지입니다. GoRouter
를 사용하면 앱의 라우팅 로직을 간결하고 직관적으로 관리할 수 있습니다. 여기서는 GoRouter
에서 자주 사용되는 몇 가지 주요 메서드를 설명하겠습니다.
context.go(String location, {Object? extra})
: 지정된 경로(location
)로 이동합니다. 선택적으로 extra
데이터를 전달할 수 있습니다. go
메서드는 현재의 네비게이션 스택을 새 위치로 대체합니다."여기로 가자!"라고 할 때 사용해요. 새로운 페이지로 완전히 이동하고 싶을 때 사용하면 돼요. 예를 들어, "집으로 가자!"라고 하면 집 페이지로 이동하게 해줘요.
context.push(String location, {Object? extra})
: 지정된 경로(location
)로 새 페이지를 푸시합니다. 이 메서드는 현재 네비게이션 스택에 새로운 항목을 추가하며, 사용자가 뒤로 가기 버튼을 눌렀을 때 이전 페이지로 돌아갈 수 있게 합니다."이 페이지도 추가해 보자!"라고 할 때 사용해요. 새 페이지를 보여주되, 뒤로 가기 버튼을 누르면 이전 페이지로 돌아갈 수 있어요.
context.pushNamed(String routeName, {Object? extra})
: 이름이 지정된 라우트로 새 페이지를 푸시합니다. GoRouter
설정에서 라우트 이름에 해당하는 경로로 이동합니다.페이지에 이름을 붙여서 그 이름으로 이동하고 싶을 때 사용해요. 예를 들어, "동물원 가자"라고 하면 동물원 페이지로 이동해요.
context.replace(String location, {Object? extra})
: 현재 위치를 지정된 경로로 교체합니다. 이 메서드는 네비게이션 스택의 현재 항목을 새 위치로 대체합니다."여기 있던 페이지 대신에 이 페이지를 보자!"라고 할 때 사용해요. 현재 페이지를 새 페이지로 바꿔요.
context.pop([Object? result])
: 네비게이션 스택에서 현재 페이지를 팝하고 이전 페이지로 돌아갑니다. 선택적으로 result
데이터를 이전 페이지로 반환할 수 있습니다."잠깐, 이전 페이지로 돌아가자!"라고 할 때 사용해요. 현재 페이지를 닫고 이전 페이지로 돌아가요.
context.refresh()
: 현재 페이지를 새로고침합니다. 이 메서드는 페이지의 상태를 재구성하는 데 사용할 수 있습니다.
"이 페이지를 새로고침하자!"라고 할 때 사용해요. 현재 페이지를 다시 불러와요.
context.goNamed(String routeName, {Object? extra})
: go
메서드의 이름 기반 버전입니다. 이름이 지정된 라우트로 이동하며, 네비게이션 스택을 새 위치로 대체합니다.이 외에도 GoRouter
에는 라우팅 로직을 보다 세밀하게 제어할 수 있는 여러 메서드와 속성들이 있습니다. GoRouter
의 메서드는 앱의 네비게이션 구조를 선언적으로 관리하면서 동시에 동적인 라우팅 요구사항을 충족할 수 있게 해 줍니다.
GoRouter
사용 시 주의할 점은, 메서드의 정확한 시그니처와 사용 방법은 GoRouter
의 버전에 따라 다를 수 있으므로, 항상 공식 문서나 해당 버전의 API 문서를 참조하는 것이 좋습니다.
알겠어요! GoRouter
는 우리가 앱에서 여러 페이지(화면) 사이를 이동할 때 사용하는 도구예요. 마치 책의 목차처럼, 어떤 페이지로 갈지 알려주는 역할을 해요. 이제 GoRouter
에서 사용하는 몇 가지 기본적인 방법들에 대해 쉽게 설명해 드릴게요.
이름을 가진 페이지로 바로 이동하고 싶을 때 사용해요. 예를 들어, "수영장 가자!"라고 하면 이름이 "수영장"인 페이지로 이동해요.
GoRouter
를 사용하면 우리가 앱에서 페이지를 이동하거나 뭔가를 변경할 때, 마치 게임에서 지도를 보고 다음 목적지를 선택하는 것처럼 쉽게 할 수 있어요. 페이지 간의 이동을 마법처럼 쉽게 도와준답니다!
네, 맞아요! GoRouter
에서 go
가 붙은 메서드와 push
가 붙은 메서드 사이에는 중요한 차이가 있어요. 이걸 쉽게 설명해 보자면, go
는 마법의 문처럼 생각할 수 있어요. 마법의 문을 통과하면 이전에 어디 있었는지 상관없이 새로운 장소로 바로 이동해요. 그리고 이전 장소로 돌아갈 수 없어요. 반면에, push
는 일종의 빵 부스러기를 뿌리며 새로운 장소로 가는 거예요. 빵 부스러기를 따라 이전 장소로 돌아갈 수 있죠.
go
사용하기: 새로운 페이지로 완전히 이동하고, 이전 페이지로 돌아갈 수 없어요. 이전 페이지들을 잊어버리고, 마법의 문을 통과해서 새로운 장소로 바로 가는 거예요.push
사용하기: 새로운 페이지로 이동하지만, 이전 페이지로 돌아갈 수 있는 길을 남겨둬요. 빵 부스러기를 뿌리듯이, 어디로 갔는지 기록을 남기고, 뒤로 가기 버튼을 누르면 그 길을 따라 이전 페이지로 돌아올 수 있어요.간단히 말해서, go
는 완전히 새로운 페이지로 "점프"하고, push
는 새로운 페이지로 "걸어가면서" 이전 페이지로 돌아올 수 있는 길을 남겨요.
이 코드 조각에서 GoRoute
를 사용하는 방법을 설명하고 있어요. GoRouter
패키지를 사용할 때, 각각의 라우트(화면 이동 경로)를 GoRoute
객체로 정의하게 되는데요, 이 객체는 앱에서 사용자가 특정 화면으로 이동할 때 어떤 화면을 보여줄지 결정하는 데 도움을 줍니다.
GoRouter
에서 state.pathParameters
와 state.uri.queryParameters
는 URL에서 정보를 추출하는 데 사용되는 두 가지 방법입니다. 이들은 사용자가 앱 내에서 특정 페이지로 이동할 때 필요한 추가 정보를 제공할 수 있게 해줍니다.
pathParameters
는 URL의 경로 부분에서 동적으로 지정된 값을 포함합니다. 예를 들어, URL이 /users/:userId
와 같이 정의되어 있고, 사용자가 /users/123
으로 이동한다면, userId
의 값은 123
이 됩니다.GoRoute
에서 경로를 정의할 때 :변수명
형식을 사용합니다. 그리고 라우트의 빌더 함수 내에서 state.pathParameters['변수명']
을 통해 해당 값을 가져올 수 있습니다.GoRoute(
path: '/users/:userId',
builder: (context, state) {
final userId = state.pathParameters['userId'];
return UserScreen(userId: userId);
},
),
queryParameters
는 URL의 쿼리 스트링 부분에서 값을 포함합니다. 쿼리 스트링은 URL에서 ?
뒤에 오며, 키-값 쌍으로 정보를 제공합니다. 예를 들어, /users?filter=active
라는 URL이 있다면, filter
의 값은 active
가 됩니다.state.uri.queryParameters['키']
를 통해 해당 쿼리 스트링의 값을 가져올 수 있습니다.GoRoute(
path: '/users',
builder: (context, state) {
final filter = state.uri.queryParameters['filter'];
return UsersScreen(filter: filter);
},
),
이렇게 pathParameters
와 queryParameters
를 사용함으로써, 앱 내에서 페이지 이동 시 필요한 추가 정보를 효율적으로 관리하고, 더 동적인 페이지 이동과 데이터 전달을 구현할 수 있습니다.
이 코드 조각은 GoRouter
를 사용하여 VideoRecordingScreen
으로의 라우트를 설정하는 예시입니다. 이 라우트 설정은 사용자 정의 페이지 전환을 포함하며, VideoRecordingScreen
으로 이동할 때 슬라이드 트랜지션을 적용합니다.
path
: 이 라우트가 반응할 URL 경로를 지정합니다. 예를 들어, VideoRecordingScreen.routeURL
이 /video-recording
이라면, 해당 경로로 이동 시 VideoRecordingScreen
이 표시됩니다.name
: 라우트의 고유한 이름을 지정합니다. 이 이름을 사용하여 프로그래매틱하게 라우트를 참조할 수 있습니다.pageBuilder
: 페이지를 생성하고, 해당 페이지에 사용할 사용자 정의 전환을 정의하는 함수입니다. CustomTransitionPage
위젯을 사용하여 이를 구현합니다.GoRoute(
path: VideoRecordingScreen.routeURL, // 라우트의 경로를 지정합니다.
name: VideoRecordingScreen.routeName, // 라우트의 이름을 지정합니다.
pageBuilder: (context, state) => CustomTransitionPage( // 페이지와 전환을 정의합니다.
transitionDuration: const Duration(milliseconds: 200), // 전환 지속 시간을 설정합니다.
child: const VideoRecordingScreen(), // 표시될 페이지(자식 위젯)를 지정합니다.
transitionsBuilder: (context, animation, secondaryAnimation, child) {
// 전환 애니메이션을 정의합니다.
final position = Tween(
begin: const Offset(0, 1), // 애니메이션 시작 위치 (화면 바닥)
end: Offset.zero, // 애니메이션 끝 위치 (화면 중앙)
).animate(animation); // 애니메이션 객체를 Tween에 연결합니다.
// SlideTransition을 사용하여 애니메이션 효과를 적용합니다.
return SlideTransition(
position: position, // 애니메이션에 따라 위치가 변화합니다.
child: child, // 전환되는 자식 위젯을 표시합니다.
);
},
),
),
transitionDuration
: 전환 애니메이션이 실행되는 시간을 지정합니다. 여기서는 200밀리초로 설정되어 있습니다.child
: 전환 효과와 함께 표시될 위젯, 여기서는 VideoRecordingScreen
의 인스턴스입니다.transitionsBuilder
: 페이지 전환 시 사용할 애니메이션을 정의하는 함수입니다. 여기서는 Tween
을 사용하여 Offset
을 조작함으로써, 화면 하단에서 시작하여 중앙으로 슬라이드되는 효과를 만듭니다.Tween<Offset>
: 시작점과 끝점을 Offset
값으로 가지는 애니메이션 값을 생성합니다. begin: const Offset(0, 1)
은 화면 바닥에서 시작하고, end: Offset.zero
는 최종적으로 화면 중앙으로 이동함을 의미합니다.animate
: Tween
에 Animation
객체를 연결합니다. 여기서는 transitionsBuilder
에 제공된 animation
을 사용합니다.SlideTransition
: 계산된 오프셋 값을 사용하여 자식 위젯을 슬라이드 시키는 위젯입니다. 이를 통해 위에서 설명한 슬라이드 효과가 적용됩니다.이 설정을 통해, VideoRecordingScreen
으로의 네비게이션 시, 사용자에게 부드러운 슬라이드 트랜지션을 제공하여 앱의 사용자 경험을 향상시킬 수 있습니다.
GoRouter
를 사용함으로써 Flutter 앱의 네비게이션 구조를 선언적으로 관리할 수 있으며, URL 기반 네비게이션, 리디렉션, 사용자 정의 페이지 전환 등의 고급 기능을 쉽게 구현할 수 있습니다. flutter_riverpod
와의 통합을 통해, 앱의 로그인 상태와 같은 상태 관리도 함께 처리할 수 있어, 보다 복잡한 앱 네비게이션 요구사항을 충족시킬 수 있습니다.