본문 바로가기
Flutter/Flutter Study

[Flutter] 위젯 간의 데이터 전송 / 스크린 이동 / Navigator, ModalRoute, Named Route

by 왁왁s 2023. 3. 16.
Contents

    이전 글에 이어서 코드를 작성하며,

    버튼을 클릭했을 때 데이터 전송까지 이루어지도록 하겠다.

     

    이전 코드는 아래의 글을 참고하자.

     

    중복된 코드를 MainLayout이라는 위젯으로 다른 파일에 빼서 관리했다.

     

    [Flutter] 버튼(Button) - ElevatedButton, OutlinedButton, TextButton

     

    [Flutter] 버튼(Button) - ElevatedButton, OutlinedButton, TextButton 꾸미기

    Contents 플러터(Flutter) 버튼(Button) 알아보기 플러터에는 ElevatedButton, OutlinedButton, TextButton 버튼 등이 있으며 이를 알아보고 style을 통해 꾸미는 방법을 알아보도록 하겠다. // ignore_for_file: prefer_const_c

    parkjh7764.tistory.com

     


     

    1. 변수와 생성자, pop을 통한 데이터 전송

    home_screen.dart와 one_screen.dart의 위젯 간의 데이터 전송을 해보도록 하자.

     

    home_screen에서 one_screen으로 데이터를 전송하기 위해 one_screen에 변수와 생성자를 선언한다.

     

    (1) 변수, 생성자 선언 

    one_screen.dart

      final String str;
    
      const OneScreen({required this.str, Key? key}) : super(key: key);

     

    (2) 받아온 데이터 띄워주기

    one_screen.dart

            Text(
              str.toString(),
              textAlign: TextAlign.center,
              style: TextStyle(
                color: Colors.red,
                fontSize: 20.0,
              ),
            ),

     

    (3) async, await를 사용해 push에서 데이터 넘겨주기

    "메인에서 넘어온 데이터"라는 String 값을 넘겨주었다.

    home_screen.dart

      @override
      Widget build(BuildContext context) {
        return MainLayout(
          title: 'Home Screen',
          children: [
            ElevatedButton(
              onPressed: () async {
                final result = await Navigator.of(context).push(MaterialPageRoute(
                  builder: (BuildContext context) => OneScreen(str: '메인에서 넘어온 데이터'),
                ));
                print(result);
              },
              child: Text('첫번째 페이지 이동'),
            ),
          ],
        );
      }

     

    그럼 아래와 같이 '첫번째 페이지 이동' 버튼을 누르면 다음 화면에 값이 전달되는 것을 확인할 수 있다.

     

     

     

    home_screen.dart 코드

    // ignore_for_file: prefer_const_constructors
    import 'package:flutter/material.dart';
    import 'package:flutter_application_arrange/layout/main_layout.dart';
    
    import 'one_screen.dart';
    
    class HomeScreen extends StatelessWidget {
      const HomeScreen({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MainLayout(
          title: 'Home Screen',
          children: [
            ElevatedButton(
              onPressed: () async {
                final result = await Navigator.of(context).push(MaterialPageRoute(
                  builder: (BuildContext context) => OneScreen(str: '메인에서 넘어온 데이터'),
                ));
                print(result);
              },
              child: Text('첫번째 페이지 이동'),
            ),
          ],
        );
      }
    }

     

    one_screen.dart 코드

    // ignore_for_file: prefer_const_constructors
    import 'package:flutter/material.dart';
    import 'package:flutter_application_arrange/layout/main_layout.dart';
    
    class OneScreen extends StatelessWidget {
      final String str;
    
      const OneScreen({required this.str, Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return MainLayout(
          title: 'First Screen',
          children: [
            Text(
              str.toString(),
              textAlign: TextAlign.center,
              style: TextStyle(
                color: Colors.red,
                fontSize: 20.0,
              ),
            ),
            ElevatedButton(
              onPressed: () {
                Navigator.of(context).pop('pop으로 넘어온 데이터');
              },
              child: Text('뒤로가기'),
            )
          ],
        );
      }
    }

     

    one_screen에서 pop으로 데이터를 넘겨 주고, home_screen에서 result로 데이터를 받아와 print를 하니 디버그 콘솔에 pop으로 넘겨준 데이터가 출력되는 것을 확인할 수 있다.

     

     

     


     

    2. Argument를 사용한 데이터 전송

    1. settings: RouteSettings(arguments:) 사용해 값 넘겨주기

    one_screen.dart

            ElevatedButton(
              onPressed: () {
                Navigator.of(context).push(
                  MaterialPageRoute(
                    builder: (BuildContext context) => TwoScreen(),
                    settings: RouteSettings(arguments: '전송한 데이터'),
                  ),
                );
              },

     

     

    2. ModalRoute.of(context) 사용해서 arguments 값 받아오기

    ModalRoute는 FullSceen 위젯인 전체 화면을 의미하는 위젯을 말한다.

    ! 느낌표는 ModalRoute를 받아올 수 없는 경우도 고려해야 하기 때문에 !를 붙여준다.

        final arguments = ModalRoute.of(context)!.settings.arguments;

     

    아래의 화면을 보면 첫번째 페이지 - 두번째 페이지 - 세번째 페이지를 넘나들며 데이터를 받아오는 것을 확인할 수 있다.

    one_screen.dart 코드

    // ignore_for_file: prefer_const_constructors
    import 'package:flutter/material.dart';
    import 'package:flutter_application_arrange/layout/main_layout.dart';
    import 'package:flutter_application_arrange/screen/two_screen.dart';
    
    class OneScreen extends StatelessWidget {
      final String str;
    
      const OneScreen({required this.str, Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return MainLayout(
          title: 'First Screen',
          children: [
            Text(
              str.toString(),
              textAlign: TextAlign.center,
              style: TextStyle(
                color: Colors.red,
                fontSize: 20.0,
              ),
            ),
            ElevatedButton(
              onPressed: () {
                Navigator.of(context).pop('pop으로 데이터');
              },
              child: Text('뒤로가기'),
            ),
            ElevatedButton(
              onPressed: () {
                Navigator.of(context).push(
                  MaterialPageRoute(
                    builder: (BuildContext context) => TwoScreen(),
                    settings: RouteSettings(arguments: '전송한 데이터'),
                  ),
                );
              },
              child: Text("두번째 페이지 이동"),
            ),
          ],
        );
      }
    }

     

    two_screen.dart 코드

    // ignore_for_file: prefer_const_constructors
    import 'package:flutter/material.dart';
    import 'package:flutter_application_arrange/layout/main_layout.dart';
    
    class TwoScreen extends StatelessWidget {
      const TwoScreen({super.key});
    
      @override
      Widget build(BuildContext context) {
        final arguments = ModalRoute.of(context)!.settings.arguments;
    
        return MainLayout(
          title: 'Two Screen',
          children: [
            Text(
              'arguments 값: ${arguments}',
              textAlign: TextAlign.center,
            ),
            ElevatedButton(
              onPressed: () {
                Navigator.of(context).pop();
              },
              child: Text('뒤로가기'),
            )
          ],
        );
      }
    }

     

     


     

    3. Named Router를 사용한 데이터 전송

     

    main.dart 파일에 routes: {} 선언

    • '/'는 Home을 의미한다. 
    • 기존에 HomeScreen()을 사용하지 않고 routes:를 사용해 Home을 설정해준다.
    • initialRoute는 초기 페이지를 설정한다.

     

    main.dart 파일

    // ignore_for_file: prefer_const_constructors
    import 'package:flutter/material.dart';
    import 'package:flutter_application_arrange/screen/home_screen.dart';
    import 'package:flutter_application_arrange/screen/one_screen.dart';
    import 'package:flutter_application_arrange/screen/three_screen.dart';
    import 'package:flutter_application_arrange/screen/two_screen.dart';
    
    void main() {
      runApp(
        MaterialApp(
          debugShowCheckedModeBanner: false,
          title: 'Appbar',
          theme: ThemeData(
            primarySwatch: Colors.purple,
          ),
          //: HomeScreen(),
          initialRoute: '/',
          routes: {
            '/': (context) => HomeScreen(),
            '/one' : (context) => OneScreen(),
            '/one/two' : (context) => TwoScreen(),
            '/one/two/three' : (context) => ThreeScreen(),
          },
        ),
      );
    }

     

    Navigator.of(context).pushNamed()를 사용해 스크린 이동

    pushNamed()는 String 값을 파라미터로 받으며, 위의 main.dart 파일에서 선언했던 라우트의 키(key) 값을 사용하면 값(value)에 해당하는 스크린으로 이동할 수 있다.

            ElevatedButton(
              onPressed: () {
                Navigator.of(context).pushNamed('/one/two/three');
              },
              child: Text('세번째 페이지 이동'),
            ),

     

     

    Navigator.of(context).pushNamed(arguemnts: )로 데이터 전송

            ElevatedButton(
              onPressed: () {
                Navigator.of(context)
                    .pushNamed('/one/two/three', arguments: '세번째 페이지 데이터 전송');
              },
              child: Text('세번째 페이지 이동'),
            ),

     

     

    ModalRoute.of(context)!.settings.arguments; 데이터 받기

    three_screen.dart 파일

        final arguments = ModalRoute.of(context)!.settings.arguments;

     

     

    two_screen.dart 코드

    // ignore_for_file: prefer_const_constructors
    import 'package:flutter/material.dart';
    import 'package:flutter_application_arrange/layout/main_layout.dart';
    
    class TwoScreen extends StatelessWidget {
      const TwoScreen({super.key});
    
      @override
      Widget build(BuildContext context) {
        final arguments = ModalRoute.of(context)!.settings.arguments;
    
        return MainLayout(
          title: 'Two Screen',
          children: [
            Text(
              'arguments 값: ${arguments}',
              textAlign: TextAlign.center,
            ),
            ElevatedButton(
              onPressed: () {
                Navigator.of(context).pop();
              },
              child: Text('뒤로가기'),
            ),
            ElevatedButton(
              onPressed: () {
                Navigator.of(context)
                    .pushNamed('/one/two/three', arguments: '세번째 페이지 데이터 전송');
              },
              child: Text('세번째 페이지 이동'),
            ),
          ],
        );
      }
    }

     

     

    three_screen.dart 코드

    // ignore_for_file: prefer_const_constructors
    import 'package:flutter/material.dart';
    import 'package:flutter_application_arrange/layout/main_layout.dart';
    
    class ThreeScreen extends StatelessWidget {
      const ThreeScreen({super.key});
    
      @override
      Widget build(BuildContext context) {
        final arguments = ModalRoute.of(context)!.settings.arguments;
    
        return MainLayout(
          title: 'Three Screen',
          children: [
            Text(
              'arguments값 : ${arguments}',
              textAlign: TextAlign.center,
            ),
            ElevatedButton(
              onPressed: () {
                Navigator.of(context).pop();
              },
              child: Text('뒤로가기'),
            )
          ],
        );
      }
    }

     


     

    유용한 Push 메서드

     

    1. pushReplacement( ) 사용해 이전 라우트 지우기

    스택에 쌓인 라우트

    [HomeScreen(), OneScreen(), TwoScreen(), ThreeScreen()]

     

    pushReplacement()를 사용하면 TwoScreen 가 지워진다.

    [HomeScreen(), OneScreen(), ThreeScreen()]

            ElevatedButton(
              onPressed: () {
                Navigator.of(context).pushReplacement(
                  MaterialPageRoute(
                    builder: (BuildContext context) => ThreeScreen(),
                  ),
                );
              },
              child: Text('pushReplacement 사용'),
            ),

     

    1-1. pushReplacementNamed( ) 사용해 이전 라우트 지우기

            ElevatedButton(
              onPressed: () {
                Navigator.of(context).pushReplacementNamed(
                  '/one/two/three',
                );
              },
              child: Text('pushReplacement 사용'),
            ),

     

    Navigator.of(context).pushReplacement()를 사용하면 아래와 같이, 

    기존에는 세번째 페이지를 이동한 후에 '뒤로가기'를 클릭하면 'Two Screen'으로 이동을 하는데

    pushReplacement()를 사용하면 Two Screen 부분이 사라지고 'First Screen'으로 이동하는 것을 확인할 수 있다.

     

     


     

     

    2. pushAndRemoveUntil( ) 사용해 이전 라우트 지우기

    해당 메서드는 삭제할 라우트의 범위를 지정할 수 있다.

            ElevatedButton(
              onPressed: () {
                Navigator.of(context).pushAndRemoveUntil(
                    MaterialPageRoute(
                        builder: (BuildContext context) => ThreeScreen()),
                    (route) => false);
              },
              child: Text('pushAndRemoveUntil 사용'),
            )

     

     

    (route)에 모든 라우트 값을 가져오고, 라우트마다 false를 반환하면 삭제가 되고, true를 반환하면 삭제가 되지 않는다.

     

    그렇다면 아래처럼 (route) => false를 주고 실행하면 모든 라우트가 삭제가 된다.

    (route) => false

    아래와 같이 pushAndRemoveUntil 버튼을 클릭했을 때 

    뒤로가기를 누르면 검은화면이 뜬다.

    이는 이전에 스택에 쌓은 모든 라우트가 삭제가 되었기 때문이다.

     

     

    named Route로 Push를 한 경우 특정 라우트까지 삭제할 수 있다.

     

    2-1. 특정 라우트까지 삭제

    아래처럼 (route) => route.settings.name == '/'를 했을 때 Home에 해당하는 '/' 사이에 존재하는 라우트를 삭제한다.

    그러면 '/'에 해당하는 부분까지 찾아가며, '/'에 해당하는 라우트가 나올 때까지 false를 리턴해 삭제를 한다.

     

    [HomeScreen(), OneScreen(), TwoScreen(), ThreeScreen()] => [HomeScreen(), ThreeScreen()]

            ElevatedButton(
              onPressed: () {
                Navigator.of(context).pushAndRemoveUntil(
                    MaterialPageRoute(
                        builder: (BuildContext context) => ThreeScreen()),
                    (route) => route.settings.name == '/');
              },
              child: Text('pushAndRemoveUntil 사용'),
            )

    뒤로가기를 눌렀을 때 HomeScreen으로 이동한다.

     

     

     

    2-2. pushNamedAndRemoveUntil( ) 사용해 이전 라우트 지우기

    Named를 사용하면 아래의 코드처럼 사용한다.

            ElevatedButton(
              onPressed: () {
                Navigator.of(context).pushNamedAndRemoveUntil(
                  '/one/two/three', (route) => route.settings.name == '/');
              },
              child: Text('pushAndRemoveUntil 사용'),
            )

     


     

     

    유용한 Pop 메서드

     

    1. Navigator.of(context).maybePop() 사용해 앱 종료 에러 막기

    가장 첫번째 라우트에서 Navigator.of(context).pop()을 했을 때, 검은 화면이 나오며 앱이 종료가 되어버린다.

            ElevatedButton(
              onPressed: () {
                Navigator.of(context).pop();
              },
              child: Text('뒤로가기'),
            )

     

    혹시나 Navigator가 잘못 설정된 경우 검은 화면을 방지하기 위해 maybePop()을 사용한다.

    maybePop()을 사용하면 더 이상 뒤로 갈 페이지가 없을 때 뒤로가기를 할 수 없도록 막아준다.

     

            ElevatedButton(
              onPressed: () {
                Navigator.of(context).maybePop();
              },
              child: Text('maybePop 뒤로가기'),
            )

     


     

    2. Navigator.of(context).canPop() 사용해 pop이 가능한지 판단

    HomeScreen에서 canPop 버튼을 눌렀을 때 false로 pop을 하면 안 된다는 것을 알려준다.

            ElevatedButton(
              onPressed: () {
                print(Navigator.of(context).canPop());
              },
              child: Text('canPop 출력'),
            ),

     

    3. WillPopScope 위젯 사용해 pop 막기

     

    안드로이드 같은 경우에 Home Screen과 같이 첫 화면에서 뒤로가기 버튼을 눌렀을 떄 앱이 종료되는 경우가 있다. 그러나 특정 앱 같은 경우엔 뒤로가기를 했을 때 종료가 되는 상황을 막아줄 필요가 있다.

     

    그때 사용하는 것은 WillPopScope 위젯이다. 

    해당 위젯의 리턴 값을 true로 하면 pop이 가능하고, false를 리턴하면 pop을 아예 불가능하게 설정할 수 있다.

     

    일부러 pop 버튼을 만들면 막을 수는 없지만 해당 버튼이 없고 사용자가 시스템상에서 뒤로가기를 했을 때 종료를 막을 수는 있다.

     

     

    • 1. WillPopScope() 위젯으로 감싸준다.
    • 2. onWillPop:의 리턴 값을 true 또는 false로 지정한다.
    • 3. onWillPop: 함수를 지정할 떄 async를 붙여준다.

     

    home_screen.dart 파일 코드

    // ignore_for_file: prefer_const_constructors
    import 'package:flutter/material.dart';
    import 'package:flutter_application_arrange/layout/main_layout.dart';
    
    import 'one_screen.dart';
    
    class HomeScreen extends StatelessWidget {
      const HomeScreen({super.key});
    
      @override
      Widget build(BuildContext context) {
        return WillPopScope(
          onWillPop: () async {
            return false;
          },
          child: MainLayout(
            title: 'Home Screen',
            children: [
              ElevatedButton(
                onPressed: () async {
                  final result = await Navigator.of(context).push(
                    MaterialPageRoute(
                      builder: (BuildContext context) =>
                          OneScreen(str: '메인에서 넘어온 데이터'),
                    ),
                  );
                  print(result);
                },
                child: Text('첫번째 페이지 이동'),
              ),
              ElevatedButton(
                onPressed: () {
                  Navigator.of(context).pop();
                },
                child: Text('뒤로가기'),
              ),
              ElevatedButton(
                onPressed: () {
                  Navigator.of(context).maybePop();
                },
                child: Text('maybePop 뒤로가기'),
              ),
              ElevatedButton(
                onPressed: () {
                  print(Navigator.of(context).canPop());
                },
                child: Text('canPop 출력'),
              ),
            ],
          ),
        );
      }
    }

     

     

    아래와 같이 canPop을 사용해 종료 조건을 줄 수가 있다.

        return WillPopScope(
          onWillPop: () async {
            final canPop = Navigator.of(context).canPop();
            return canPop;
          },

    댓글