(번역) 플러터 Push, Pop, Push

ARi·2023년 2월 18일
0

Translation

목록 보기
1/2

원문: https://medium.com/flutter-community/flutter-push-pop-push-1bb718b13c31

프레임워크가 제공하는 위젯들을 사용하여 플러터로 UI를 만드는 것은 꽤 쉬운 일입니다. 하지만 기능이 없고 보기만 좋은 어플리케이션은 필요하지 않습니다. 여기저기 클릭하거나 데이터를 주고 받고 다른 화면으로 이동할 수 있어야 합니다. 플러터에서 다른 화면으로 이동은 Navigators를 사용합니다. Navigators는 스택의 Routes를 기억하는데, 다시 쉽게 말하자면 이전의 화면과 페이지들을 가지고 있다는 것입니다.

시작 전에,

Routes에 대해서 알아보겠습니다.

Routes는 app의 화면 또는 페이지를 위한 개념(abstraction)입니다. 예를 들면 '/home'을 HomeScreen으로 이동하거나 '/login'은 LoginScreen으로 이동할 것입니다. '/'는 가장 처음 페이지의 기록입니다. 마치 REST API에서 길찾기를 하는 것과 비슷하므로 '/'는 기록의 뿌리, 처음과 같은 역할을 하게 됩니다.

아래 코드에서 플러터 어플이 어떻게 routes를 사용하는지 확인할 수 있습니다.

new MaterialApp(
  home: new Screen1(),
  routes: <String, WidgetBuilder> {
    '/screen1': (BuildContext context) => new Screen1(),
    '/screen2' : (BuildContext context) => new Screen2(),
    '/screen3' : (BuildContext context) => new Screen3(),
    '/screen4' : (BuildContext context) => new Screen4()
  },
)

Push,push,push

자료구조(Data Structures)에 대하여 안다면 Stacks일 알고 있을 것입니다. 또한 스택에 대한 기본 지식이 있다면 pushpop도 알 것 입니다.

만약 알지 못한다면, 'push 한다'는 것은 파일처럼 차곡차곡 쌓여있는 어떤 것의 가장 윗부분에 새로운 것을 더하는 것이고. 'pop 한다'는 것은 반대로 가장 윗부분의 것을 없애는 것입니다.

이 개념을 플러터 대입한다면, 다른 화면으로 전환할 때 push 메서드나 Navigator 위젯으로 스택의 가장 윗부분에 새로운 화면을 쌓아줍니다. 그리고 pop 메서드는 스택에서 화면을 제거할 때 사용됩니다.

그럼 Screen1에서 Screen2로 이동하는 것을 코드로 확인하겠습니다.

new RaisedButton(
   onPressed:(){
   Navigator.of(context).pushNamed('/screen2');
},
   child: new Text("Push to Screen 2"),
),

정말 짧죠!

pushNamed 메서드로 main.dart에 정의한 루트로 어떤 화면이든지 이동할 수 있습니다. 이것을 바로 namedRoute라고 부릅니다. 간결하게 페이지 이동을 할 수 있습니다!

Pop it

이제 가장 최근에 접속했던 화면을 제거하겠습니다. 여기에선 Screen2이겠죠. pop 메서드를 사용해서 네이게이터의 스택으로부터 Routes를 없앨 수 있습니다.

Navigator.of(context).pop();

이 코드는 onpressed 메서드 안에서 사용된 것을 기억해주세요.

Screen2를 없앤 후의 스택

Scaffold는 자동적으로 Navigator.pop()이라고 불리는 'back'버튼을 AppBar에서 사용할 수 있기 때문에 루트를 삭제하기 위해서 이 방식이 전혀 필요하지 않습니다. 하지만 사용자가 취소버튼을 눌러서 AlertDialog를 제거할 때처럼 이 방식이 필요할 때도 있습니다.

그렇자면 왜 이전 페이지로 다시 돌아가서 스택에 쌓아주는 대신에 현재 페이지를 제거하는 방식을 사용할까요?
자, 가보고 싶은 장소의 호텔 목록을 호텔 예약 어플에서 가지고 있다고 상상해보세요. 목록에서 아무 호텔이나 클릭을 하면 그 호텔에 대한 사세 페이지로 화면이 이동합니다. 선택했지만 그 호텔이 싫은 경우 다시 호텔 목록을 보고 싶겠죠? 만약 hotelListScreen르로 다시 돌아간다면 매번 돌아갈 때마다 여러 호텔의 DetailsScreen이 스택에 쌓일 것입니다. 그리고 back 버튼을 누르면 DetailsScreen으로 돌아가는 것입니다. 헷갈리죠! 아래를 통해 한번더 이 개념을 정리하겠습니다.

sample app을 실행해보겠습니다. Screen1에서 appBar를 확인해보세요. 맨 처음 루트(또는 home screen)이기 때문에 여기에는 back 버튼이 없습니다. back 버튼없이 Screen2로 Push 하세요. 그리고 pop를 하지 않고 다시 Screen1을 Push 하겠습니다. 이제 Screen1에서 appBar를 확인하세요. 새로 나타난 back 버튼을 누르면 원하지 않더라도 다시 Screen2로 돌아갈 것입니다.

위의 설명의 스택

maybePop

만약 처음 페이지에 있고 누군가가 실수로 이 화면을 없애려고 시도한다면 어떻게 될까요? 더이상 보여줄 루트가 없기 때문에 스택에 하나 뿐이었던 처음 페이지는 사라지고 어플리케이션은 꺼질 것입니다 당연히 사용자가 이런 상황을 경험하게 하고 싶지 않겠죠. 그래서 바로 maybePop()이 있는 것입니다. 할 수 있을 때먄 pop을 하는 것입니다. Screen1에서 maybePop 버튼을 클릭해도, 더이상 pop할 수 있는 것이 없기 때문에 아무일도 일어나지 않을 것입니다. 이제 Screen3에서 maybePop 버튼을 만들고 실행하면 화면이 그 전으로 전환될 것입니다.

canPop

하지만 지금 있는 페이지가 기본 페이지인지 어떻게 알 수 있을까요? 만약 이런 경우에 사용자에게 돌아갈 페이지가 없는 처음 페이지임을 알려줄 수 있다면 좋을 것입니다. 그럴 때는 canPop() 메서드를 사용해서 돌아갈 페이지가 있으면 그 페이지 스택을 삭제 후에 true로 그 전 페이지로 돌아가고, false면 동작하지 않습니다.

Push a little harder

push 메서드에 대해 더 알아보도록 하겠습니다. 이번에는 pushReplacementNamed와 popAndPushNamed 이 2개의 메서드를 가지고, 하나의 루트를 새로운 루트도 대체하는 것에 대하여 알아보겠습니다.

Navigator.of(context).pushReplacementNamed('/screen4');
                       //and
Navigator.popAndPushNamed(context, '/screen4');

스택 전 후

sample app의 Screen3로 2개 모두 시도해보겠습니다. 어떤 게 나가고 어떤 게 추가되는지 주의해서 봐주세요. pushReplacementNamed는 들어오는 것을 popAndPushNamed는 나가는 것이 실행될 것입니다. 아래에 따라오는 예시 상황을 통해서 확인해보겠습니다.

pushReplacementNamed

사용자가 로그인을 성공하고나서 DashboardScreen에 있다고 하다면, 이 경우에 사용자를 다시 LoginScreen으로 이동하지 않게 하고 싶을 것입니다. 그래서 login route는 dashboard route로 바꿔야합니다. 또다른 예는, SplashScreen에서 HomeScreen으로 이동하는데, 이것은 한번만 보여져야하고 사용자는 HomeScreen에서 다시 SplashScreen으로 돌아가면 안될 것입니다. 이런 경우에 우리는 새로운 화면으로 완전히 대체하기 위해서 pushReplacementNamed 메서드를 사용합니다.

popAndPushNamed

지금 ProductsListScreen에서 제품의 목록을 보여줄 쇼핑 어플리케이션을 만들고 있는 중이고 사용자는 FiltersScreen으로 원하는 카테고리별로 확인할 수 있다고 가정해봅시다. 사용자가 Apply Changes 버튼을 클릭할 때, FilterScreen은 삭제되고 다시 ProductsListScreen으로 새로 설저한 카테고리로 정리된 목록의 페이지를 보여줘야합니다. 이런 상황에서 popAndPushNamed를 사용하면 됩니다.

거의 끝입니다!

여기에서는 우리는 pushNamedAndRemovedUntil과 popUntil를 알아보겠습니다.

pushNamedAndRemoveUntil

페이스북이나 인스타그램 같은 어플리케이션을 만든다면, 사용자는 로그인하여 친구들의 피드를 보거나 프로필 사진을 통해 모르는 사람들이 업로드한 것들을 구경할 것입니다. 그리고나서 어플리케이션을 로그아웃을 할 것입니다. 이런 경우에는 주의해야 하는 것이 단순히 HomeScreen이나 로그아웃 후에 보여질 수 있는 어떤 화면으로든지 push하면 안된다는 것입니다. 스택 안의 모든 routes를 삭제하기 원하면 사용자는 로그아웃하기 전에 변경한 이전의 routes들로 돌아갈 수 없을 것입니다.

Navigator.of(context).pushNamedAndRemoveUntil('/screen4', (Route<dynamic> route) => false);

(Route route) => false 덕분에 push된 루트를 제거하기 전에 모든 루트들을 확인할 수 있습니다.

모든 루트를 제거하고 로그아웃하고나서 다시 사용자에게 LoginScreen를 보여줍니다.

push된 루트들 전에 모든 루트들을 제거하기 전에, 특정한 번호들의 루트들만 제거할 수 있는 방법을 알아보겠습니다.

이런 종류의 어플리케이션에서 일단 사용자가 결제를 마치고나면 장바구니나 결제 페이지 화면은 스택으로부터 사라져야합니다. 그리고 사용자는 paymentConfirmationScreen 페이지로 이동해야합니다. 만약 back 버튼을 누른다면 ProductsListScreen 또는 HomeScreen으로만 이동하게 될 것입니다.

Navigator.of(context).pushNamedAndRemoveUntil('/screen4', ModalRoute.withName('/screen1'));

위의 코드를 보면 Screen4를 push하면서 Screen1 전까지의 모든 루트들은 제거하였습니다.

popUntil

구글폼같은 어떤 양식을 적어야하는 어플리케이션을 만든다고 상상해보세요. 이제 사용자는 3개의 페이지를 가진 질문지에 답변을 쓰고나면 3개의 연속적인 화면이 어플리케이션에 보여지게 될 것입니다. 이제 3번째 부분의 페이지에서 사용자가 구글폼을 작성하는 것을 취소하려고 합니다. 사용자는 Cancel을 누를 것이고 화면들과 연관된 이전의 모든 페이지들은 사라지고 사용자는 작성한 데이터들을 모든 잃은 채로 HomeScreen 또는 DashboardScreen으로 이동되야 할 것입니다. 여기에서 새로운 어떤 것을 push하는 것이 아니라 이전 route로 이동해야합니다.

구글폼과 연관된 모든 화면이 사라졌습니다.

Navigator.popUntil(context, ModalRoute.withName('/screen2'));

그럼 데이터는 어디에?

위의 대부분은 실제로 어플리케이션에서는 거의 일어나지 않는 상황인, 어떠한 데이터의 이동없이 새로운 루트를 push하는 예시였습니다. 데이터를 보내는 방법은, 데이터를 가지고 스택으로 새로운 MaterialPageRoute로 push하기 위해서는 Navigator를 사용하는 것입니다.

String userName = "John Doe";
Navigator.push(
  context,
  new MaterialPageRoute(
      builder: (BuildContext context) =>
      new Screen5(userName)));

Screen5안에 userName을 가져오기 위해서 매개변수가 있는 생성자(parameterized constructor)를 더해줍니다.

class Screen5 extends StatelessWidget {

  final String userName;
  Screen5(this.userName);
  @override
  Widget build(BuildContext context) {
  print(userName)
  ...
  }
}

push를 위해서는 MaterialPageRoute뿐만 아니라 pushReplacement, pushAndPopUntil 등도 사용할 수 있습니다. Basically drop the named keyword from the above methods we described, and the first parameter will now take a MaterialPageRoute instead of a String of namedRoute.

Give me some data back, man

만약 새로운 화면으로부터 데이터를 돌려받기 원할지도 모릅니다. Alarm app을 가정해보세요. 그리고 새로운 알랍 소리를 설정하기 위해서 알람소리옵션 목록이 있는 Dialog를 보여줘야할 것입니다. 우선 분명히 하나의 알람소리 데이터 아이템을 선택하면 Dialog는 사라질 것입니다.

String value = await Navigator.push(context, new MaterialPageRoute<String>(
    builder: (BuildContext context) {
      return new Center(
        child: new GestureDetector(
            child: new Text('OK'),
            onTap: () { Navigator.pop(context, "Audio1"); }
        ),
      );
    }
)
);
print(value);

},
child: new Text("Return"),)

Screen4에서 시도해보세요. 그리고 values값을 console에서 체크해보세요.

📢 하나의 루트가 value를 리턴하기 위해 사용되어질 때 그 루트 타입의 매개변수는 pop의 결과 타입과 일치해야합니다. 그래서 여기에서 String 타입의 데이터이 필요하기 때문에 MaterialPageRoute을 사용했습니다. (정확한 타입 설정을 하지 않아도 괜찮습니다)

profile
하이하이

0개의 댓글