Flutter 일기
참고1 : https://api.flutter.dev/flutter/widgets/Navigator-class.html
참고2 : https://medium.com/flutter-community/flutter-push-pop-push-1bb718b13c31
참고3 : https://origogi.github.io/flutter/flutter-push-pop-push/ (참고2를 번역해서 포스팅한 오리고기씨의 블로그)
Navigator
지난 일기에 이어서 Navigator 를 이어가보자. (독서여행 떠났다가 한달만에 쓰려니 어색하네)
Navigator는 Stack구조를 활용해 화면 전환을 하는 클래스이다. 저번 일기에서는 MaterialPageRoute를 이용했는데, 이번에는 route를 활용해 페이지들을 나열한 후, 라우트 이름으로 페이지 전환을 해보자.
우선 라우트는 목적지 네트워크로 가는 이동 경로를 말한다. 코드에서는 이러한 경로에 이름을 붙여 사용할 것이다. 그냥 첫번째 페이지, 두번째 페이지에 이름을 붙여 사용하는 것이라고 보면 되겠다. 코드는 저번 일기에서 사용한 것을 조금만 변형해 사용할 것이고, 왕창 바뀌는 건 아니니 바로 코드를 보자.
코드 예시로 알아보자
이번에는 다른 기능들도 추가했기 때문에, 페이지가 하나 늘어나서 코드가 좀 길다. [코드-설명] 식이다. 전체 코드는 다 이어붙이면 실행 가능하다.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: 'SubPage Example',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity),
initialRoute: '/',
routes: {
'/': (context) => FirstPage(),
'/second': (context) => SecondPage(),
'/third': (context) => ThirdPage(),
},
);
}
}
class FirstPage extends StatelessWidget {
const FirstPage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Main Page'),
),
body: Container(
child: Center(
child: Text(
'첫 번째 페이지',
style: TextStyle(fontSize: 30),
),
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.arrow_forward),
onPressed: () {
// Navigator.of(context).push(
// MaterialPageRoute(
// builder: (context) => SecondPage(),
// ),
// );
Navigator.of(context).pushNamed('/second');
},
),
);
}
}
class SecondPage extends StatelessWidget {
const SecondPage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Second Page'),
),
body: Center(
child: ElevatedButton(
child: Text(
'세번째 페이지로',
style: TextStyle(fontSize: 20),
),
onPressed: () {
Navigator.of(context).pushReplacementNamed('/third');
},
),
),
);
}
}
덮어쓰는 것이 아닌 '대체'라서, Stack에서 아예 사라져버리기 때문에 뒤로 가도 해당 페이지가 없다.
(저번 시간에는 named route를 쓰지 않았는데, 안쓰고 MaterialPageRoute를 쓰려면 Named가 안붙은 함수를 쓰면 된다.)
-> 여기 실행화면을 보자
class ThirdPage extends StatelessWidget {
const ThirdPage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Third Page'),
),
body: Center(
child: ElevatedButton(
child: Text(
'첫번째 페이지로',
style: TextStyle(fontSize: 20),
),
onPressed: () {
Navigator.of(context)
.pushNamedAndRemoveUntil('/', ModalRoute.withName('/'));
},
),
),
);
}
}
Future<T?> pushNamedAndRemoveUntil<T extends Object?>(
String newRouteName,
RoutePredicate predicate, {
Object? arguments,
}) {
return pushAndRemoveUntil<T>(_routeNamed<T>(newRouteName, arguments: arguments)!, predicate);
}
내가 쓴 것은 필수 인자인 newRouteName, predicate 부분을 넣은 것인데, newRouteName 에 해당하는 route로 가면서, predicate 부분에서 true를 반환할 때까지 이전 라우트들을 몽땅 지워버린다.
ㅜㅜ뭐라는지 모르겠어서 코드를 이것저것 바꿔봤다. 함수 사용법은 참고2의 페이지에서 소개하며, 참고3의 오리고기 개발자님이 번역을 해두신 문서가 있다.
Navigator.of(context)
.pushNamedAndRemoveUntil('/', ModalRoute.withName('/'));
요기서 withName안의 인자를 바꿔보자.
그리고 SecondPage 클래스에서 pushReplacementNamed -> pushNamed 로 바꿔준다. (Stack에서 어느 페이지까지 지워버리는지 알기 위함이다.)
onPressed: () {
// Navigator.of(context).pushReplacementNamed('/third');
Navigator.of(context).pushNamed('/third');
},
실행화면을 보자.
withNamed('/') | /second |
---|---|
/third | (route) => false |
---|---|
가장 첫번째 페이지로 이동하면서, Stack에 어떤 페이지가 남아있는지 확인해보았다.
먼저 withNamed('/') 라고 쓰게 되면, 첫번째 페이지만 남겨놓고 그 위에 쌓인 페이지들은 다 제거한다. 그리고 첫번째 페이지로 이동하므로 Stack에는 FirstPage가 두개 쌓여있는 형태이다.
second, third도 유사하다. 해당 페이지 직전까지의 요소들을 Stack에서 다 빼버리고 새로 첫번째 페이지를 Stack에 쌓는 것이다.
(Route<dynamic> route) => false
위와 같이 쓰게 되면, 이전에 Stack에 쌓아둔 모든 경로가 제거된다. 그래서 맨 오른쪽의 gif파일을 보면, 세번째 페이지에서 첫번째 페이지로 이동하면 뒤로 가기 버튼이 없다. 갈 곳이 없기 때문이다.
그런데 이런 기능들은 어디에서 쓰는가?
대표적인 예시는 로그인이다. 로그인이나 회원가입에 성공하면 회원가입 페이지로는 돌아갈 일이 없다. 그럴 경우 Stack에서 로그인 페이지를 빼주어 뒤로가기를 연타하는 상황에서도 로그인 페이지가 나타나지 않도록 해준다.
또는 로그아웃이나 회원탈퇴를 하면, 앱의 처음화면으로 돌아가는 것을 본 적이 있을 것이다. 이 경우 이전 경로를 깡그리 삭제하고 첫화면으로 돌아간다. 이러한 상황에서 주로 사용한다.
난 Flutter 배우기 전에는 앱개발 지식이 전무해서 처음에는 '이거 왜써...?' 라고 생각했다.
저번 일기를 짧게 썼더니 이번 일기가 왕창 늘어나버렸네
오늘의 일기는 여기까지!