Things take time

[Flutter] Route간 이동 및 데이터전달 본문

Flutter

[Flutter] Route간 이동 및 데이터전달

겸손할 겸 2020. 7. 14. 14:41

[Route]

안도르이드의 액티비티, iOS의 뷰 컨트롤러에 대응되는 개념인 Route, 이 루트간 이동하는 방법은 iOS와 마찬가지로 다양한 방법이 존재한다.

 

안드로이드처럼 startActivity계열로 깔끔하게 사용하면 좋으련만

 

 

[단순 이동]

방법은 두 가지가 있는데, 공통적으로 Navigator객체를 사용하여 이동하는 방식이나 사용법이 다르다.

 

1)

                Navigator.of(context).push(
                  MaterialPageRoute(
                    builder: (context) {
                      return SecondPage();
                    },
                  ),
                );

첫 번째 방법은 Navigator에 push로 넣되, 직접 MaterialPageRoute를 사용하는 방법이다. 여기서 추가로 데이터를 전달하려면, SecondPage의 생성자를 하나두고 SecondPage('data')를 하면 된다. 

 

2)

Navigator.of(context).pushNamed('/second');

두 번째 방법은 pushName을 통해 직접 문자열을 입력하는 방법인데, '/second'라는 문자열을 루트 테이블에 등록해야한다. 이는 build함수 단에서 MaterialApp을 리턴할때만 사용할 수 있다. 단순 Container나 Scaffold를 리턴하는 등의 뷰에서는 사용할 수 없으므로 유의한다.

 

MaterialApp위젯의 속성 중 routes라는 속성이 있는데, 이는 루트 테이블에 등록한다고 표현하기도 한다.

      initialRoute: '/',
      routes: {
        '/': (context) => MyHomePage(),
        // '/second': (context) => SecondPage(), :: 같은 방식 // + onGenerateRoute 사용 시 routes 테이블 등록 X
        SecondPage.secondPageRouteName: (context) => SecondPage('가나다라마'),
      },

initialRoute라는 속성을 사용한 줄을 해석하면, 테이블 루트에 '/'로 등록된 루트를 찾아서 body 속성에 넣겠다는 것이다.

그러므로 initialRoute를 사용할때는 MaterialApp의 body 프로퍼티를 사용하지 않는다.(사용 시 에러)

 

그리고 중요한 routes프로퍼티에 등록된 문자열을 등록하고, 리턴되는 루트들을 파악하면 된다.

'/'를 사용하여 루트를 열 경우, MyHomePage라는 위젯 클래스를 열 것이고, '/second'문자열로 루트를 열 경우,  SecondPage()라는 위젯을 연다는 의미이다. 여기서 secondPageRouteName이란 것은 SecondPage클래스에서 아래와 같이 선언했기에 이처럼 사용했다. ('가나다라마'는 아래에서 추가 설명시 사용)

class SecondPage extends StatelessWidget {
  static const String secondPageRouteName = "/second";
  
  ...

 

즉, 단순 코드로 해당 클래스를 직접 적어 호출하는 방법과, 루트 테이블에 미리 등록해서 해당 문자열을 등록하는 방법이 있는데 레퍼런스상으로는 루트 테이블에 등록하고 사용하길 추천한다. (named route 라고 표현하기도 함)

 

 

[데이터 전달 이동]

사실 단순 이동은 어렵지 않고, 안드로이드 & iOS에서도 그러하듯, 데이터를 전달해서 사용하는 경우가 더 많으므로 이 방법은 꼭 숙지하자.

 

플러터 답게, 도큐먼트가 너무 잘 되있으므로 아래를 참고해도 좋다.

https://flutter.dev/docs/cookbook/navigation/navigate-with-arguments

 

Pass arguments to a named route

How to pass arguments to a named route.

flutter.dev

1)

레퍼런스에서 루트 테이블을 이용하여 미리 등록하는것을 추천한다고 했다. 위와 같이 루트 테이블을 등록했다 가정하고, 루트를 이동하는 함수를 아래와 같이 변경한다.

                Navigator.pushNamed(
                  context,
                  SecondPage.secondPageRouteName,
                  arguments: Arguments('Abcde', '12345'),
                );

즉, pushgNamed를 사용했으나 그 안에 들어가는 파라미터가 3개로 늘어났다. 첫 번째 파라미터는 현재 루트이며 두 번째 파라미터는 루트 테이블에 등록된 문자열, 세 번째 파라미터가 실제 데이터이다. 여기선 Arguments라는 클래스 생성자를 전달했는데 아래와 같이 Arguments 클래스도 하나 생성해 줘야 한다.

 

Arguments.dart

// 넘겨질 파리미터들의 클래스
class Arguments {
  final String args1;
  final String args2;

  Arguments(this.args1, this.args2);
}

 final 변수는 선언과 동시에 초기화 되어야하는 변수이므로, 생성자에 바로 초기화 되도록 구성되어있다.

 

자, 그럼 SecondPage에서는 데이터를 받아 처리해야 한다.

 

SecondPage.dart

class SecondPage extends StatelessWidget {
  static const String secondPageRouteName = "/second";
  final String getValue;
  
  // 정적 데이터 전달
  SecondPage(this.getValue);

  @override
  Widget build(BuildContext context) {
    // 동적인 데이터 전달
    Arguments arguments = ModalRoute.of(context).settings.arguments;

    // TODO: implement build
    return Scaffold(
      body: Center(
        child: Container(
            width: 100,
            height: 200,
            color: Colors.blue,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(getValue),
                Text(arguments != null ? arguments.args1 : ""),
                Text(arguments != null ? arguments.args2 : ""),
              ],
            )),
      ),
    );
  }
}

여기서 동적인 데이터 전달 쪽을 보면, 모달로 띄워진 루트의 세팅 값중 arguments라는 객체는 Arguments라는 클래스 객체에 담는 작업을 하고 있다. 이렇게 하면 Text()안에 데이터를 받을 수 있다. 여기서 Arguments라는 클래스는 내가 만든 클래스이므로, settings.arguments라는 관련이 없다! 다른 클래스로 만들어서 받아도 된다. settings.arguments는 다트 내부 객체이다.

 

여기서 한 가지 더 볼 것은 정적 데이터 전달이라고 한 것이다. final 변수 getValue는 SecondPage를 호출할 때 생성자로 넘겨받는 파라미터로 초기값을 할당했는데, 위의 단순 이동 방법 중 두 번째 방법으로 소개한 곳에서 '가나다라마'를 생성자로 넣었다는 것을 기억하자.

 

그리고 실행한다면 아래와 같은 결과를 얻는다.

 

가나다라마는 루트를 호출할 때, 루트 테이블 안에 생성자로 넣은 것이고 Abcde, 12345는 데이터를 전달받은 것임을 유의하자. 이를 통해 왜 정적 데이터인지, 동적 데이터인지를 알 수 있을 것이다.

 

2)

두 번째 데이터를 전달하여 루트를 여는 방법은 루트 테이블을 사용하지 않는 방법이다.  호출하는 방식은 첫 번째 방법대로 호출하나 루트 테이블에 등록하지 않고, MaterialApp의 프로퍼티 중 onGenerateRoute를 사용한다. 

      routes: {
        '/': (context) => MyHomePage(),
        // '/second': (context) => SecondPage(), :: 같은 방식 // + onGenerateRoute 사용 시 routes 테이블 등록 X
        SecondPage.secondPageRouteName: (context) => SecondPage('가나다라마'),
      },
      // 이 방식 사용시, home : 속성을 사용하지 않음
      onGenerateRoute: (settings) {
        if (settings.name == ThirdPage.thirdPageRouteName) {
          final Arguments args = settings.arguments;
          return MaterialPageRoute(
            builder: (context) {
              return ThirdPage(
                args1: args.args1,
                args2: args.args2,
              );
            },
          );
        }
        assert(false, 'Need to implement ${settings.name}');
        return null;
      },

루트 테이블은 참고하기 위해 같이 올렸다. 세 번째 루트는 루트테이블에 등록하지 않고 onGenerateRoute에서 사용한다. 

참고로 assert라는 함수는 값 혹은 정규식을 취하는 함수인데, 값이 만약 false라면 AssertionError를 뱉는다고 한다. 즉, 위 소스는 ThirdPage에만 되어있는데, 이 외의 페이지이면서 루트 테이블에도 등록되지 않는다면 에러메시지와 함께 null을 리턴한다는 것이다.

위의 소스에서 루트테이블에서 SecondPage 부분을 주석하고 실행하면 assert가 발생되므로 참고!

 

그리고 이제 데이터를 받는 루트 쪽에서 사용하는 방법!

 

ThirdPage.dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class ThirdPage extends StatelessWidget {
  static String thirdPageRouteName = "/third";

  final String args1;
  final String args2;

  const ThirdPage({
    Key key,
    @required this.args1,
    @required this.args2,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
      child: Container(
        alignment: Alignment.center,
        width: 100,
        height: 200,
        color: Colors.yellow,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(args1),
            Text(args2),
          ],
        ),
      ),
    ));
  }
}

 이전 방법은 ModalRoute.of(context).settings.arguments 방식을 사용하지 않고 생성자를 통해 데이터를 받는 방식이다.

                Navigator.pushNamed(
                  context,
                  ThirdPage.thirdPageRouteName,
                  arguments: Arguments('Abcde', '12345'),
                );

이 코드가 돌게 되면, 루트 테이블에 등록되었는지 판단하고 없으므로 onGenerateRoute가 돌게 되며, 이 안에서 name프로퍼티로 구별한다. 그리고 MaterialPageRoute를 직접 리턴하는 방식이다. 

 

실행 결과는 아래와 같다.

뭐가 나은지는 모르겠지만, 루트 테이블에 등록하는게 편한 것 같다.

 

 

* pushNamed vs push의 추천

https://resocoder.com/2019/04/27/flutter-routes-navigation-parameters-named-routes-ongenerateroute/

 

Flutter Routes & Navigation – Parameters, Named Routes, onGenerateRoute - Reso Coder

Subscribe Get the f​ull project Routing is one of the most basic things your app must have to do anything meaningful. However, navigating between pages can quickly turn into a mess. It doesn't have to be so! There are multiple options for routing. Some

resocoder.com

해당 페이지에서는 

기본 push방법을 이용한 뷰 이동 및 데이터 전달은 작은 프로젝트에선 유용할지 모르나, 복잡한 앱이나 인증된 사용자등에 관해서는 코드 중복을 피하기 위해 루트 테이블에 등록하여 사용할 것을 추천하고있다.

 

 

** Navigator.of.pushNamed vs Navigator.pushNamed

동일하다, pushNamed를 사용하면 자동적으로 of를 붙여 변환, 컴파일 되므로 자유롭게 사용하면 된다.