[flutter] Navigation(이론) (+ 중복값 위젯컴포넌트만들기)

jini.choi·2024년 4월 10일
0

flutter

목록 보기
8/9

중복되는 레이아웃 위젯으로 만들기

  • 만약 Scaffold()를 포함한 구성들이 AppBar의 title 텍스트와 Column의 Children가 없다면?
    (NavigationScreen과 구성이 동일한 다른 Screen이 있을 때) (아래 코드는 안봐도됨)
import 'package:flutter/material.dart';
import 'package:flutter_ver1/pages/jin_navigation/route_one_screen.dart';

class NavigationScreen extends StatefulWidget {
  const NavigationScreen({super.key});

  @override
  State<NavigationScreen> createState() => _NavigationScreenState();
}

class _NavigationScreenState extends State<NavigationScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("NavigationScreen"),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            ElevatedButton(
              onPressed: () {
                Navigator.of(context).push(
                  MaterialPageRoute(
                    builder: (BuildContext context) {
                      return const RouteOneScreen();
                    },
                  ),
                );
              },
              child: const Text("Push!"),
            )
          ],
        ),
      ),
    );
  }
}
  • Scaffold가 감싸고 있는 코드를 Widget으로 빼서 새로운 파일에 작성을 해준다.
  • 구성에서 Screen들의 다른 점이 뭔지 파악해서 변수 선언 해주고 부모한테 받아오기 위해 컨스트럭트에 추가해준다.(지금은 title, children이 다름)
import 'package:flutter/material.dart';

class MainLayout extends StatelessWidget {
  final String title;
  final List<Widget> children;💡

  const MainLayout({💡
    super.key,
    required this.title,
    required this.children,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),💡
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: children,💡
        ),
      ),
    );
  }
}
  • 다시 NavigationScreen과 구성이 동일한 다른 Screen에 새로 만든 MainLayout 위젯을 불러와서 title, children값을 인자로 넘겨준다.
import 'package:flutter/material.dart';
import 'package:flutter_ver1/pages/jin_navigation/layout/main_layout.dart';
import 'package:flutter_ver1/pages/jin_navigation/route_one_screen.dart';

class NavigationScreen extends StatefulWidget {
  const NavigationScreen({super.key});

  @override
  State<NavigationScreen> createState() => _NavigationScreenState();
}

class _NavigationScreenState extends State<NavigationScreen> {
  @override
  Widget build(BuildContext context) {
    return MainLayout( //MainLayout 가져와서 인자로 title, children넘겨줌
      title: 'NavigationScreen',💡
      children: [💡
        ElevatedButton(
          onPressed: () {
            Navigator.of(context).push(
              MaterialPageRoute(
                builder: (BuildContext context) {
                  return const RouteOneScreen();
                },
              ),
            );
          },
          child: const Text("Push!"),
        )
      ],
    );
  }
}

Navigation - 값 주고받기

첫번째 방법

RouteOneScreen에서 NavigationScreen에게 숫자를 전달받는다고 하면

  • final로 number를 받는다 선언하고 컨스트럭트에 추가
  • 잘 받는지 테스트 Text(number.toString()),
class RouteOneScreen extends StatelessWidget {
  final int number;💡

  const RouteOneScreen({super.key, required this.number});💡

  @override
  Widget build(BuildContext context) {
    return MainLayout(
      title: 'RouteOneScreen',
      children: [
        Text(number.toString()),💡
        ElevatedButton(
          onPressed: () {
            Navigator.of(context).pop();
          },
          child: const Text("POP!"),
        )
      ],
    );
  }
}
  • NavigationScreen에서는 이제 필수로 RouteOneScreen에 number를 인자로 넘겨줘야한다.
ElevatedButton(
          onPressed: () {
            Navigator.of(context).push(
              MaterialPageRoute(
                builder: (BuildContext context) {
                  return const RouteOneScreen(number: 123);💡
                },
              ),
            );
          },
          child: const Text("Push!"),
        )

RouteOneScreen에서 다시 데이터를 돌려준다면?

  • RouteOneScreen : 아무것도 안하고 그냥 인자에 값만 입력
ElevatedButton(
          onPressed: () {
            Navigator.of(context).pop(456);💡
          },
          child: const Text("POP!"),
        )
  • NavigationScreen : 미래에 받는거기 때문에 async로 감싸주고 result에 담아준다.
ElevatedButton(
          onPressed: () async {
            final result = await Navigator.of(context).push(💡
              MaterialPageRoute(
                builder: (BuildContext context) {
                  return const RouteOneScreen(number: 123);
                },
              ),
            );

            print(result);
          },
          child: const Text("Push!"),
        )

두번째 방법 : Argument 전달하기(값 전달)

  • RouteOneScreen에서 RouteTowScreen로

  • RouteOneScreen

    • onPressed를 하면 MaterialPageRoute를 새로 stack이 push되면서 settings값을 추가해서 arguments를 전달할 수 있다.
ElevatedButton(
          onPressed: () {
            Navigator.of(context).push(
              MaterialPageRoute(
                builder: (BuildContext context) {
                  return const RouteTowScreen();
                },
                settings: const RouteSettings(💡
                  arguments: 789,
                ),
              ),
            );
          },
          child: const Text("Push!"),
        )
  • RouteTowScreen는 ModalRoute()클래스를 변수에 담아줌 of(context)를 붙여서 위젯트리에서 가장 가까운 ModalRoute를 가져옴 final argument = ModalRoute.of(context);

    ModalRoute() : 풀스크린의 위젯의미

  • RouteTowScreen 스크린에서 가장 위젯 트리에 가까운 풀스크린 라우트는 그냥 RouteTowScreen이다.

  • RouteTowScreen여기서 값을 가져와야하는 거임 RouteOneScreen에서 settings안에 arguments를 넣어 놨기 때문에 다음 코드와 같이 값을 가져옴
    final argument = ModalRoute.of(context)!.settings.arguments;
    (특정상황에서 null이 있을수 있음 하지만 지금은 없기 때문에 null있을 수 없다는 의미의 !를 입력해준다.)

Widget build(BuildContext context) {
    final argument = ModalRoute.of(context)!.settings.arguments;💡

    return Scaffold(
      body: MainLayout(
        title: 'RouteTowScreen',
        children: [
          Text(
            'argument : ${argument.toString()}',💡 // 값 들어오는지 확인
            textAlign: TextAlign.center,
          ),
          ElevatedButton(
            o

Navigation - Named Routed

위 방법들보다 이거 쓰는데 BEST💡

  • RouteTowScreen
ElevatedButton(
            onPressed: () {
              Navigator.pushNamed(context, '/three', arguments: 901);
            },
            child: const Text('RouteThreeScreen'),
          ),

Navigation - 알고있으면 좋은 Push메서드

replace

pushReplacement(newRoute) - 일반 push랑 비슷
pushReplacementNamed(routeName) - 일반 Named Routed랑 비슷

  • 기존에는 NavigationScreen에서 RouteOneScreen push하고, RouteOneScreen에서 RouteThreeScreen push 하고 RouteTwoScreen에서 RouteThreeScreen push하면

  • Stack은[NavigationScreen(), RouteOneScreen(), RouteTwoScreen(), RouteThreeScreen()]
    순서대로 오른쪽으로 push가되고 왼쪽부터 pop이된다.

  • 하지만 아래 코드와 같이 pushReplacement를 하거나 pushReplacementNamed onPressed했을 때 RouteTwoScreen()를 Stack에서 지워버리기 때문에 RouteThreeScreen에서 pop을 했을 때 Stack에서 RouteOneScreen으로 이동된다.
    Stack -[NavigationScreen(), RouteOneScreen(), RouteThreeScreen()]

ElevatedButton(
            onPressed: () {
              Navigator.of(context).pushReplacement(MaterialPageRoute(
                  builder: (BuildContext context) => const RouteThreeScreen()));
            },
            child: const Text("pushReplacement!"),
          )

pushAndRemoveUntil

pushAndRemoveUntil(newRoute, (route) => false)
pushNamedAndRemoveUntil(routeName, (route) => false)

  • 일반 라우트처럼 첫번째 인자에 MaterialPageRoute()를 넣어주고,(네임드일때는 경로이름)

  • 두번째 인자인 (route) => false에 조건별로 원하는 값을 넣어준다.

 ElevatedButton(
            onPressed: () {
              Navigator.of(context).pushAndRemoveUntil(
                  MaterialPageRoute(
                      builder: (BuildContext context) =>
                          const RouteThreeScreen()),
                  (route) => false);
            },
            child: const Text("pushAndRemoveUntil!"),
          )
  • 만약 (route) => false 를 그대로 두고 RouteThreeScreen로 이동하면 RouteThreeScreen제외하고 라우트가 모두 지워진다.
    • push했을 때 Stack - [RouteThreeScreen()]
    • ture면 안지워짐
  • 그래서 (route) => route.settings.name == '/' 이런식으로 조건을 걸어줘서 name이 '/'일때만 안지우고 나머지 라우트로 이동하면 다 지우게 할 수 있다.
ElevatedButton(
            onPressed: () {
              Navigator.of(context).pushAndRemoveUntil(
                  MaterialPageRoute(
                      builder: (BuildContext context) =>
                          const RouteThreeScreen()),
                  (route) => route.settings.name == '/');
            },
            child: const Text("pushAndRemoveUntil!"),
          )

Navigation - 알고있으면 좋은 Pop메서드

maybePop

  • 더이상 현재 Screen제외하고 Stack에 아무것도 없을 때 pop을 해도 뒤로가지 않게 막아주는 메서드
  • homeScreen에서 실수 방지에 용이함
ElevatedButton(
          onPressed: () {
            Navigator.of(context).maybePop();
          },
          child: const Text("maybePop!"),
        ),

canPop

  • maybePop를 넣고 뒤에 어떤 페이지가 있는지 없는지 알 수 없을 때 canPop를 통해 print를 찍어서 존재여부를 알 수 있다.(다른 기능은 하지 않고 true, false만 찍힘)

  • 더 이상 현재 Screen제외하고 Stack에 아무것도 없을

  • 현재 Screen제외하고 뒤에 Stack에 다른 Screen이 있을 때

ElevatedButton(
          onPressed: () {
            print(Navigator.of(context).canPop());
          },
          child: const Text("conPop!"),
        ),

PopScope 위젯 (3.12버전 이하에서 WillPopScope(이제 못씀))

  • 현재 Screen제외하고 더 이상 라우트가 존재하지 않는다면 뒤로가기를 막아야하는 경우에 사용
    (안드로이드의 경우 기본시스템으로 뒤로가기가 있기 때문)
return PopScope(
    canPop: true, //false인 경우 현재 경로가 pop되지 않도록 차단
    onPopInvoked: (didPop) {
        //do your logic here:
        setStatusBarColor(
            statusBarColorPrimary,
            statusBarIconBrightness: Brightness.light,
        );
        finish(context);
    },
    child: Scaffold(/*other child and ui etc*/),
);
profile
개발짜🏃‍♀️

0개의 댓글