Flutter Naviation
go_router | Flutter Package
get | Flutter Package
이번 글에서는 Flutter에서 화면간 이동에 대한 방법을 알아보려고 한다.
아마도 개발의 가장 중요하면서도 기본이 되는 기능이기 때문에 초보자들도 이미 사용하고 있었던 기능일 것이다. 간단하게 알아보고자 한다.
Flutter에서 페이지 간 이동을 하는 방법은 기본적으로 Flutter에 내장되어 있는 Navigator를 이용하는 방법과 라이브러리를 사용하는 방법이다.
저는 개인적으로 Navigator 방식을 사용하지는 않는다. 아무래도 푸시, 공유 등의 기능을 사용할 때 딥링크를 전달받아 라우팅을 결정하게 되는데, Navigator 방식은 코드 작성도 길어지고 조금 불편하다.
제가 주로 사용하는 Get 라이브러리의 라우팅 기능과 go_router 라이브러리에 대해서도 추가적으로 알아보도록 하겠다.
Bloc 또는 Provider 상태관리를 사용하는 경우에는 go_router를 주로 사용하고, Get X 상태관리를 사용하는 경우에 Get 라우팅 기능을 사용하고 있다.
기본적으로 Flutter에서 페이지 이동시 페이지가 Stack 구조로 쌓이게 되어 push-pop 과정을 거치게 된다. push를 통해서 페이지 위에 페이지를 쌓게 되고, pop을 시켜 쌓여 있는 Stack의 가장 최상단 페이지 부터 제거하게 된다.
먼저 Flutter 기본 라우팅인 Navigator에 대해서 알아보도록 하자.
화면을 이동할 때 페이지를 오픈하는 가장 기본적인 방법이다. push를 통해서 사용할 수 있으며, route는 MaterialPageRoute를 사용하여 열고자 하는 위젯을 등록하는 방법이다.
Navigator.of(context).push(MaterialPageRoute(builder: (context) => const FirstPage()));
push로 페이지를 오픈할 때에 데이터를 전달하고자 한다면, 오픈하는 페이지 위젯의 생성자로 받고자 하는 변수를 등록해주고, router에 전달할 위젯에 파라미터로 넘겨주면 데이터를 전달할 수 있다.
final String title;
const FirstPage({
super.key,
required this.title,
});
Navigator.of(context).push(MaterialPageRoute(builder: (context) => FirstPage(title: "Test")));
이번에는 위젯을 전달하는게 아닌 라우터를 등록해 놓고 편하게 라우터 이름으로 접근하는 기능에 대해서 알아보고자 한다.
MaterialApp 위젯에 routes 부분에 key-value 형태로 사용하고자 하는 라우터 네임과 위젯을 등록해준다.
routes: {
"/first": (context) => const FirstPage(),
},
pushNamed를 사용하여 등록한 라우터의 이름만 넣어 간단하게 페이지를 오픈할 수 있다.
Navigator.of(context).pushNamed("/first");
push에서 데이터를 전달하는 것과 같이 pushNamed에서 데이터를 전달하는 방법이다.
argument를 오픈하는 페이지에서 ModalRoute를 통해서 얻을 수 있다.
build(BuildContext context) {
Map<String, dynamic> _argument =
ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>;
print(_argument["title"]);
...
}
Widget
전달할 때에는 arguments에 전달하고자 하는 데이터를 key-value 형태로 넣어주면 된다.
Navigator.of(context).pushNamed("/first", arguments: {"title": "Test"});
pushAndRemoveUntil은 페이지를 오픈할 때에 스택으로 쌓여있는 페이지를 제거하고 이동하는 방법이다.
MaterialPageRoute에 이동하고자 하는 페이지를 넣어주고 어느 페이지까지 스택을 제거할지를 넣어주면 되는데, false인 경우 모든 페이지를 제거하고 이동한다는 의미이다.
Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(
builder: (context) => const HomePage()), (route) => false);
이번에는 위에서 살펴본 방법의 named 방식이다.
Navigator.of(context).pushNamedAndRemoveUntil("/home", (route) => false);
페이지 하나만 제거하고 이동할 때에 popAndPushNamed 기능을 사용할 수 있는데, 해당 기능은 named 방식만 제공하고 있다.
main -> FirstPage -> SecondPage -> popAndPushNamed(ThirdPage) 순서로 페이지 이동 후 뒤로 가보면 SecondPage는 제거되 있는 것을 확인할 수 있다.
Navigator.of(context).popAndPushNamed("/second");
이동시 페이지를 제거하고 이동하는게 아닌 페이지를 현재 이동하고자 하는 페이지로 교체하면서 이동할 수 있는 방법이 pushReplacement이다.
popAndPushNamed와 비슷하지만 다르다. 페이지가 제거되는게 아닌 교체된다고 보면된다.
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => const ThirdPage()));
pushReplacement의 named 방식이다.
Navigator.of(context).pushReplacementNamed("third");
이번에는 쌓여있는 스택을 하나씩 닫고 이전 페이지로 돌아가는 방법에 대해서 알아보고자 한다.
간단하게 pop을 사용하여 페이지를 닫아 쌓여있는 스택을 제거하면서 페이지 이동을 한다.
Navigator.of(context).pop();
이전 페이지로 이동할 때에도 데이터를 전달할 수 있는데, pop 기능에 parameter를 전달할 수 있고, 해당 parameter는 Object로도 전달할 수 있다.
Navigator.of(context).pop("callBack");
이번에는 페이지를 닫을 수 있는지 닫을 수 없는지에 대해서 제공해주는 기능으로 boolean 값을 리턴하여 현재 페이지에서 더 이상 닫을 수 있는 페이지가 있는지에 대한 정보를 제공받을 수 있다.
Navigator.of(context).canPop();
maybePop은 페이지를 닫을 수 있으면 닫고, 닫을 수 없으면 더 이상 닫지 않는 기능을 제공해주는데, Flutter에서는 더 이상 페이지를 닫을 수 없을 때 pop을 하게 되면 블랙스크린이 노출되게 된다.
이럴 경우에 대비해서 사용할 수 있지만, maybePop 기능은 페이지 스택 구조 상에 페이지 뎁스 구조를 잘 설계해야 지만 정상적으로 작동이 된다.
Navigator.of(context).maybePop();
popUntil은 모든 페이지를 닫는 방법으로 위에서 살펴본 until 기능과 동일하다. 다만 popUntil은 페이지를 열고 닫는게 아닌 바로 페이지를 닫는 기능으로 isFirst를 사용하게 되면, 최하단 스택 라우터를 제외하고 닫아주게 된다.
Navigator.of(context).popUntil((route) => route.isFirst);
이번에는 Get 라이브러리리의 Router에 대해서 살펴보고자 한다. Navigator에서 이미 살펴본 기능과 사용 방법만 다를 뿐 같은 기능인 경우가 많기에 필요한 설명만 하도록 하겠다.
dependencies:
get: ^4.6.5
먼저 Get 라우트를 사용할 때에도 named 방식을 지원하는데, 아래와 같이 GetPage를 먼저 등록해야 한다.
GetMaterialApp(
getPages: [
GetPage(name: "/home", page: ()=> const HomePage()),
],
);
== Navigator.push(context)
Get.to(() => const FirstPage());
== Navigator.pushNamed(context)
Get.toNamed("/first");
네임드 방식을 사용할 때에는 Get에서 아래와 같이 path 형태로 데이터를 전달할 수 있다. 이 방식으로 deeplink를 사용하여 푸시, 공유하기 기능에 적용할 수 있다.
GetPage(name: "/home:id", page: ()=> const HomePage()),
Get.toNamed("/first/$id");
오픈하는 페이지에서 아래와 같은 형태로 파라미터를 얻을 수 있다.
final int id = int.parse(Get.parameters['id'].toString());
== Navigator.pushReplacement(context)
Get.off(() => const FirstPage());
== Navigator.pushReplacementNamed(context)
Get.offNamed("/first");
모든 페이지를 제거하고 이동하는 방법이다.
Get.offAll(() => const FirstPage());
offAll의 named 방식이다.
Get.offAllNamed("/first");
== Navigator.popAndPushNamed(context)
Get.offAndToNamed("/second");
== Navigator.popUntil(context)
Get.until((route) => Get.currentRoute == '/home');
== Navigator.pushAndRemoveUntil(context)
Get.offUntil(GetPageRoute(page: () => const ThirdPage()),
(route) => (route as GetPageRoute).routeName == '/first');
== Navigator.pushNamedAndRemoveUntil(context)
Get.offNamedUntil("/third",
(route) => (route as GetPageRoute).routeName == "/first");
== Navigator.pop(context)
Get.back();
이전 페이지에 데이터 전달시 result에 파라미터를 넘겨줄 수 있다.
Get.back(result: true);
이번에는 go_router 라이브러리에 대해서 알아보고자 한다. 이전에 go_router를 사용하기 전에는 Beamer라는 라이브러리를 사용해서 라우팅 처리를 하였는데, Beamer 라이브러리의 이슈가 많아 go_router로 변경한 이후로는 메인으로 사용하고 있는 라이브러리이다.
위에서 살펴본 Naviation, Get Route와 사용 방법은 거의 유사하다고 보면되지만, 서브트리 구성과 리다이텍트 등의 기능을 쉽고 편하게 사용할 수 있게 해준다.
기존 방법들과 차이가 있는 개념이 있는데, 여기서는 location으로 작동이 된다는 것이다. 여태까지는 위젯 또는 네임드 형태의 라우팅을 했었는데, go_router는 path 개념으로 라우팅을 할 수 있다는 것이다.
물론 네임드를 지정하여 네임드 방식도 사용 가능하다.
dependencies:
go_router: ^6.2.0
go_router를 사용하기에 앞서 routerConfig를 등록하여야 하는데 MaterialApp을 Materal.router로 변경해 주고 아래와 같이 등록하면 된다. initialLocation에 앱 초기 로케이션을 넣어주면 된다.
return MaterialApp.router(
routerConfig: GoRouter(initialLocation: "/main",
routes: [
GoRoute(path: "/main", builder: (context, state) => const HomePage()),
GoRoute(
path: "/first",
name: "first",
builder: (context, state) => const First(),
),
]),
);
path 형태의 라우팅 사용 방법이다.
context.go("/first");
context.push("/first");
context.pushReplacement("/first");
routerConfig GoRoute에 name을 명시해주고 사용하여야 한다.
context.goNamed("first");
context.pushNamed("first");
context.pushReplacementNamed("first");
우선 데이터를 페이지 간에 전달하는 방법에 대해서 알아보자.
First 페이지에서 title이라는 String 변수를 하나 받아오도록 하자.
class First extends StatelessWidget {
final String title;
const First({
super.key,
required this.title,
});
...
}
GoRoute 위젯의 path를 아래와 같이 수정하고, builder 부분에서 First 위젯을 리턴해보자. 이 때 title 값으로 state.params의 title로 넣어주면 된다. Object도 지정할 수 있다.
GoRoute(
path: "/first/:title",
name: "first",
builder: (context, state) {
return First(
title: state.params["title"].toString(),
);
}),
이렇게 호출하여 페이지를 오픈하면 열린 페이지에서 정상적으로 데이터가 전달된 것을 확인할 수 있다.
context.go('/first/test');
context.push('/first/test');
GoRouter.of(context).go('/first/test');
GoRouter.of(context).push('/first/test');
이번에는 go_router의 가장 강력한 기능이라고 생각하는 subRoutes 기능이다. 만약에 First 페이지를 열고 First 페이지 안에 있는 FirstSub 페이지를 오픈하고 싶다고 가정해 보자. 아마도 라우터를 2번 생성하거나 First페이지에서 함수를 작동시켜 sub 페이지를 오픈하거나 했을 것이다.
go_router에서는 이러한 기능을 간단하게 구현할 수 있도록 해준다.
GoRoute를 아래와 같이 수정하고 routes에 새로운 라우터를 추가해 보자. routes에 서브 라우터를 등록할 수 있다.
FirstSub 페이지에서 count 값을 받아오는 라우터를 서브 라우터로 생성하였다.
GoRoute(
path: "/first",
builder: (context, state) {
return const First();
},
routes: [
GoRoute(
path: "firstSub/:count",
builder: (context, state) {
return FirstSub(
count: int.parse("${state.params["count"]}"));
})
]),
이제 실행해보면 First 페이지가 열리고 FirstSub 페이지가 오픈되는 것을 확인할 수 있다.
context.go("/first/firstSub/123");
이번에는 First 페이지에서도 파라미터를 넘겨주어야 한다고 가정하고 코드를 수정해보자.
First 페이지에서는 문자열 변수 title을 필수 값으로 받아오게 했다.
GoRoute(
path: "/first/:title",
name: "first",
builder: (context, state) {
return First(
title: "${state.params["title"]}",
);
},
routes: [
GoRoute(
path: "firstSub/:count",
builder: (context, state) {
return FirstSub(
count: int.parse("${state.params["count"]}"));
})
]),
FirstSub 페이지에 전달한 123 숫자가 정상적으로 노출되는 것을 확인했고, 뒤로 나와보면 First 페이지에서도 test라는 문자열이 정상적으로 전달된 것을 확인할 수 있다.
context.go("/first/test/firstSub/123");
context.pop();
GoRouter.of(context).pop();
이렇게 페이지 간 이동하는 방법인 라우터에 대해서 살펴보았다. 여러분은 어떤 방식이 가장 편하고 사용하기 좋다고 생각하는가 ?
앱 프로젝트가 처음 생성되고 초기 작업시에는 라우터의 중요성을 인식하지 못하고 개발하는 경우가 종종 있다. 하지만 대다수의 앱에서는 푸시, 공유, 앱 간 통신 등의 기능을 활용하고 있기에 딥링크를 전달 받아 라우팅을 해줄 수 있는 방법을 개발하게 된다.
이럴 때를 대비해서 초기 시작시 라우터 환경을 미리 작업해 두는 것이 좋다. Get 라이브러리를 상태관리로 사용하지 않는다면, Get Router를 위해서 Get을 사용하는 것은 비추한다. go_router를 사용하길 추천한다. 하지만 Get 상태관리를 사용하려고 한다면 Get의 Router를 사용하는게 더 효율적이라고 생각이 된다.
라우터의 기본적인 작동 방법에 대해서 살펴봤으니, 실제 앱을 개발할 때에 라우터 환경을 어떻게 등록하고 처리하는 지에 대해서도 앱 개발 프로젝트를 진행하면서 더 깊이 있게 다뤄보도록 하겠다.