(Flutter) 위젯 간 데이터 전송 / 화면 이동 / Navigator, ModalRoute, Named Route

  • by
콘텐츠


    이전 기사 뒤에 코드를 작성하고,

    버튼을 클릭할 때 데이터 전송까지 수행되도록 합니다.

    이전 코드는 아래 문서를 참조하십시오.

    중복 코드 MainLayout라는 위젯으로 다른 파일로 당겨 관리했다.

    (플러터) 버튼(버튼) – ElevatedButton, OutlinedButton, TextButton

    (Flutter) 버튼 (Button) – ElevatedButton, OutlinedButton, TextButton 장식

    Contents Flutter 버튼(Button)을 배우는 플러터에는 ElevatedButton, OutlinedButton, TextButton 버튼 등이 있습니다.

    //ignore_for_file:prefer_const_c

    parkjh7764.


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

    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:를 사용하여 홈을 설정합니다.

    • 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 파일에서 선언한 루트의 키 값을 사용하여 값(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('뒤로가기'), ) ), ); } }


    편리한 푸시 방법

    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() 를 사용하면 다음과 같이,

    기존에는 3페이지를 이동한 후 뒤로를 클릭하면 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 사용'),
            )

    (루트)에 모든 루트 값을 가져오고 각 루트에 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('뒤로가기'),
            )

    네비게이터가 올바르게 설정되지 않은 경우 검은 화면을 방지하기 위해 maybePop()사용합니다.

    maybePop() 을 사용하면 더 이상 뒤로 페이지가 없을 때 되돌릴 수 없습니다.


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


    2. Navigator.of(context).canPop() 사용하여 팝이 가능한지 결정하기


    HomeScreen에서 canPop 버튼을 누를 때 false로 팝하지 마십시오.


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

    3. WillPopScope 위젯을 사용하여 pop을 방지

    Android와 같은 경우 Home Screen과 같이 첫 화면에서 돌아가기 버튼을 누르면 앱이 종료될 수 있다.

    그러나 특정 앱과 같은 경우에는 돌아가기를 할 때 종료되는 상황을 막을 필요가 있다.

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

    해당 위젯의 반환 값을 true로 설정하면 pop이 가능하고 false를 반환하면 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;
          },