JK : 그래서 Navigator Push랑 Pop이랑 차이가 뭐야?
rimi : 그야 Navigator가 stack이니까 push는 새로 쌓이는거고 pop은 현재껄 빼는 거니까 그전 화면으로 돌아가는거지!
JK : 그럼 pop대신 push를 써도 되지않아? 굳이 왜 pop을써?
rimi : push하면 계속 쌓이잖아! 굳이 왜 이전 화면을 새로만들어??
JK : 그럼 지금 이거 중간에 지우는게 없는데 계속 쌓이는거아니야?
rimi : (오싹) 그러게 push가 새로 쌓이는건지 밑에서 불러와서 쌓이는건지 확실하지않넹(긁적)
그럼 중간에 pop으로 빼야되나?
JK : 우움우움~ JK는 뒤로가기 시룬걸~ 믿을수없는걸~ 가끔씩 메모리 엉키는거얼~
(대충 니가 알아서 어떻게해보라는 뜻)
rimi : (1차 빡침) 어차피 StreamBuilder인데 굳이 다 지우고 새로 안 부르고 pop으로 처리하면 되지않을강 ^^? 어차피 비동기라 데이터 바뀌면 알아서 바뀔텐뎅?^^
JK : 우움우움~ JK는 라떼라서 믿을 수 없는 거얼~ (대충 그건 싫으니 하지말라는 뜻)>
rimi : (2차 빡침)...
그는 날카로웠지만 좀 빡쳤다. 아니 난 저렇게 깊게 생각하지않는 타입이라(이렇게 한수 배워갑니다)
역시 주니어와 시니어의 차이란...(이건 그냥 사람의 차이)
지금까지 만들었던 코드를 사용자의 입장으로 하나하나 타고가면서 stack으로 그림으로 그려본다
일단 시나리오는 앱을 처음 다운로드해서 처음키고 정상하차가 아닌 강제하차일때
정상하차일때는 pop으로 빼내어주고 있지만, 강제하차는 '정말 강제로 하차하시겠습니까?'라는 팝업을 띄우고 push로 보낸다. 여기서 문제가 발생하는것, 사용자가 계속 강제하차를 했을때는 무한으로 계속 스크린이 쌓이게된다. 그렇게 되면 메모리를 엄청나게 잡아먹겠지?
일단 문제인 곳을 파악하고 정리해본다.
그럼 여기서 그냥 push쓴 곳을 pop으로 바꾸면 되는거아니야?
아니다. 일단 우리가 받은 요구사항은 "JK : 우움우움~ JK는 라떼라서 믿을 수 없는 거얼~ (대충 그건 싫으니 하지말라는 뜻)" 이것이므로 스택에 있는 걸 다 지우고 새로 만든다.
나는 화면을 전환할때 아래의 코드를 사용한다.
Navigator.push(context, MaterialPageRoute(builder: (context) =>
UploadScreen()));
모든 화면 전환을 저 코드로 사용하고있는데, 보통의 방법은 아니라고한다. 그럼 보통의 방법으로 변경해보자
일단 MainApp에서 MaterialApp에 대한 정의를 내릴때 각각 screen에 대한 routes를 적는다.
먼저 경로를 따로 지정해준다. 나는 따로 items 디렉토리에 routes.dart 파일을 생성하여 넣어주었다.
class Routes {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case '/':
return CupertinoPageRoute(builder: (_) => Index(), settings: settings);
case '/transform':
return CupertinoPageRoute(
builder: (_) => WorkScreens(), settings: settings);
case '/initial':
return CupertinoPageRoute(
builder: (_) => RegisterInitial(), settings: settings);
case '/worklog':
return CupertinoPageRoute(
builder: (_) => RegisterWorkLog(), settings: settings);
case '/upload':
return CupertinoPageRoute(
builder: (_) => UploadScreen(), settings: settings);
case '/download':
return CupertinoPageRoute(
builder: (_) => DownloadScreen(), settings: settings);
case '/report':
return CupertinoPageRoute(
builder: (_) => PerformanceReport(), settings: settings);
case '/info':
return CupertinoPageRoute(
builder: (_) => ChageCarNumber(), settings: settings);
case '/dispatch':
return CupertinoPageRoute(
builder: (_) => DispatchList(), settings: settings);
}
}
}
현재 CupertinoTabScaffold을 이용하여 CupertinoTabView로 보여주고있으므로, CupertinoPageRoute를 이용하도록 하겠다.
그리고 MainApp에 적용해준다.
class MainApp extends StatelessWidget {
// This widget is the root of your application.
Widget build(BuildContext context) {
return MaterialApp(
title: 'B2Care',
theme: ThemeData(
primarySwatch: Colors.blue,
),
debugShowCheckedModeBanner: false,
onGenerateRoute: Routes.generateRoute,
// named routes 설정
initialRoute: '/',
// 초기페이지 로드
);
}
}
그치만 이래도 에러가 뜨기 시작했다. 에러의 내용인 즉슨
Unhandled Exception: Could not find a generator for route RouteSettings("/worklog", null) in the _CupertinoTabViewState.
Generators for routes are searched for in the following order:
1. For the "/" route, the "builder" property, if non-null, is used.
2. Otherwise, the "routes" table is used, if it has an entry for the route.
3. Otherwise, onGenerateRoute is called. It should return a non-null value for any valid route not handled by "builder" and "routes".
4. Finally if all else fails onUnknownRoute is called.
Unfortunately, onUnknownRoute was not set.
ㅎ... 구글링을 아무리 찾아보아도 나오지않는 결과.... 결국 CupertinoTabView 내부에서 찾았다.
command 키를 누르면 CupertinoTabView 클래스의 내부를 볼수있는데, 저기서 onGenerateRoute를 받는걸 알수있다.
여기서 "MainApp에서도 초기에 받지않았나? 저기 안에도 routes를 따로 넣어줘야하나?" 라는 개발자의 킹리적 갓심을 해본다.
일단 의심이 되면 넣어보고 생각하는거다.
build(BuildContext context) {
return CupertinoTabScaffold(
// ~ 생략 ~
tabBuilder: (context, index) {
return CupertinoTabView(
onGenerateRoute: Routes.generateRoute,
// 일단 MainApp에서 넣었던 그대로 넣어준다
builder: (context) {
// builder는 탭 index마다 다르므로 case문을 이용해 적어준다.
switch (index) {
case 0:
return ChageCarNumber();
case 1:
return WorkScreens();
case 2:
return DispatchList();
}
},
);
});
}
Widget
그리고 돌아가는 걸 확인한다.
페이지를 이동할때 이전 페이지가 아닌 새로운 페이지로 넘길때는 Push를 이용하면되는데,
일단 새로운 페이지를 push하려면 아래와 같이 쓴다.(스택의 개념을 생각하자)
Navigator.of(context).pushNamed('/upload');
// or
Navigator.pushNamed(context, '/upload');
계속 쌓이는 것을 방지하기 위해 아래와 같이 현재 페이지와 넘길 페이지를 대체 할수있다.
(더 이상 이전 페이지로 돌아가지 않아도 될때 사용)
나같은 경우는 앞서 말한것처럼 계속 쌓이게 되는 문제가 되는부분(DownloadScreen -> UploadScreen)으로 갈때 사용하였다.
Navigator.of(context).pushReplacementNamed('/upload');
쌓여진 모든 페이지를 제거하고 새로 불러올땐 아래와 같이 사용한다.
Navigator.of(context).pushNamedAndRemoveUntil('/upload', (Route<dynamic> route) => false);
이전페이지로 돌아가고 싶을때는 아래와 같이 사용하면된다.
Navigator.of(context).pop();
참고 :
1. https://medium.com/flutter-community/flutter-push-pop-push-1bb718b13c31
2. https://api.flutter.dev/flutter/cupertino/CupertinoTabView-class.html
엌ㅋ 제목 너무 웃겨요 ㅋㅋ
flutter 공부하면서 시리즈들 정주행했습니다 많이 배워갑니다