Foggy day

[Flutter] Navigator(화면이동) - 사용법(1부) 본문

Flutter/Flutter widget

[Flutter] Navigator(화면이동) - 사용법(1부)

jinhan38 2023. 3. 31. 00:02

 

 

이번 포스팅은 Flutter Navigator의 사용법에 대해 알아보겠습니다. 

 

Flutter에서 화면을 이동하는 방법은 개발자마다 다른 것 같습니다. Flutter에서 제공하는 기본적인 Navigator로만 사용하는 경우가 있고, Getx나 go_router를 사용하는 분들도 있습니다. 이번에는 Dart에서 제공하는 기본적인 방법을 배워보고 추후에 기회가 되면 다른 방법도 알아보겠습니다. 

 

Navigator사용법에 대한 설명은 길어질 것 같아서 1, 2부로 나눴습니다. Navigator를 상세하게 다루기엔 범위가 넓고, 코드 파악에만 많은 시간을 투자해야 합니다. 때문에 간단한 개념과 사용법을 위주로 다루겠습니다.

 

 

 

 

1. Route와 Navigator

2. push, pop

3. pushReplacement, pushAndRemoveUntil, popUntil

4. removeRoute

 

 

 

 

1. Route와 Navigator

Flutter의 화면 이동을 한 줄로 정의해 보자면 'Navigator로 Route들을 관리한다'라고 생각합니다.

그럼 Navigator와 Route가 무엇일까요?

Route :  screen이나 page라고 불리는 위젯
Navigator : Route(위젯)을 관리하는 위젯

 

Flutter는 모든 것이 위젯이라는 말이 있습니다. 화면 이동 또한 위젯의 범위 안에 있습니다. 페이지(Route)를 만들어서 Navigator로 이동하는 개념입니다. 물론 더 상세히 들여다보면 복잡한 구조가 있지만 핵심만 간단히 짚고 넘어가겠습니다.

 

 

 

2. push, pop

push는 다른 페이지로 이동, pop은 이전 페이지로 이동하는 것의 의미합니다. push와 pop 함수는 Navigator에 있습니다.

 

push

push를 사용하기 위해서는 context와 route가 필요합니다. route에는 MaterialPageRoute 클래스를 사용합니다. MaterialPageRoute는 이동하려는 Screen을 Route로 만들어 준다고 이해하면 됩니다.

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

 

pop

pop은 context만 넘겨주면 되므로 push에 비해 간단합니다.

Navigator.pop(context);

 

 

 

3. pushReplacement, pushAndRemoveUntil, popUntil

 

pushReplacement

pushReplacement는 현재 페이지를 다른 페이지로 대체하는 함수입니다. 함수 이름은 다르지만 구현 형태는 push와 같습니다.

Navigator.pushReplacement(
  context,
  MaterialPageRoute(
    builder: (context) {
      return RouteSecondScreen();
    },
  ),
);

 

pushAndRemoveUntil

특정 화면으로 이동하고, 기존에 있는 화면을 모두 제거합니다. 

Navigator.pushAndRemoveUntil(
  context,
  MaterialPageRoute(
    builder: (context) {
      return const HomeScreen();
    },
  ),
  (route) => false,
);

 

popUntil

첫번재 라우트까지 이동하고, 그 외의 화면들은 종료합니다. 

Navigator.popUntil(
  context,
  (route) {
    return route.isFirst;
  },
)

 

 

 

 

4. removeRoute

removeRoute함수는 특정 Route(스크린)을 지우는 기능입니다. 하지만 구현하기가 생각보다 까다로웠습니다. Route 스택들을 관리해줘야 했기 때문입니다. 

구현 방법은 아래와 같습니다.

  • Navigator.push 함수를 호출할 때 만든 MaterialRoute를 Map에 저장해 놓습니다.
  • removeRoute 함수를 호출할 때 Map에 저장해 놓은 route가 있다면 removeRoute를 호출합니다.

 

addRoute함수에서 route를 생성했습니다. 그리고 생성한 route를 routes에 저장하고 있습니다.

removeRoute함수에서는 특정 스크린 이름으로 routes 변수에 추가된 것이 있다면 저장해 놓은 route를 불러와서 remove 합니다. 그리고 다시 해당 routes의 값은 초기화합니다. 

Map<String, MaterialPageRoute<dynamic>?> routes = {
  'route_screen': null,
  'route_second_screen': null,
  'route_third_screen': null,
};

void addRoute({required String screen, required Function(MaterialPageRoute route) callback }) {
  MaterialPageRoute? route;
  switch (screen) {
    case 'route_screen':
      route = MaterialPageRoute(builder: (context) => const RouteScreen());
      break;
    case 'route_second_screen':
      route = MaterialPageRoute(builder: (context) => const RouteSecondScreen(text: "stack"));
      break;
    case 'route_third_screen':
      route = MaterialPageRoute(builder: (context) => const RouteThirdScreen());
      break;
  }
  routes[screen] = route;
  callback(route!);
}


void removeRoute(BuildContext context, String screen) {
  if (routes[screen] != null) {
    Navigator.removeRoute(context, routes[screen]!);
    routes[screen] = null;
  }
}

 

 

removeRoute full code

import 'package:flutter/material.dart';

Map<String, MaterialPageRoute<dynamic>?> routes = {
  'route_screen': null,
  'route_second_screen': null,
  'route_third_screen': null,
};

void addRoute({
  required String screen,
  required Function(MaterialPageRoute route) callback,
}) {
  MaterialPageRoute? route;
  switch (screen) {
    case 'route_screen':
      route = MaterialPageRoute(builder: (context) => const RouteScreen());
      break;
    case 'route_second_screen':
      route =
          MaterialPageRoute(builder: (context) => const RouteSecondScreen());
      break;
    case 'route_third_screen':
      route = MaterialPageRoute(builder: (context) => const RouteThirdScreen());
      break;
  }
  routes[screen] = route;
  callback(route!);
}

void removeRoute(BuildContext context, String screen) {
  if (routes[screen] != null) {
    Navigator.removeRoute(context, routes[screen]!);
    routes[screen] = null;
  }
}

class RouteScreen extends StatelessWidget {
  const RouteScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("RouteScreen"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () {
                addRoute(
                  screen: "route_second_screen",
                  callback: (route) => Navigator.push(context, route),
                );
              },
              child: const Text("Go second"),
            ),
          ],
        ),
      ),
    );
  }
}

class RouteSecondScreen extends StatelessWidget {
  const RouteSecondScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("RouteSecondScreen"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () {
                addRoute(
                  screen: "route_third_screen",
                  callback: (route) => Navigator.push(context, route),
                );
              },
              child: const Text("Go third"),
            ),
          ],
        ),
      ),
    );
  }
}

class RouteThirdScreen extends StatelessWidget {
  const RouteThirdScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("RouteThirdScreen"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
                onPressed: () {
                  removeRoute(context, "route_second_screen");
                },
                child: const Text("Route secondScreen")),
          ],
        ),
      ),
    );
  }
}

 

 

 

 

 

 

참고자료

https://docs.flutter.dev/cookbook/navigation/navigation-basics

 

Navigate to a new screen and back

How to navigate between routes.

docs.flutter.dev

https://docs.flutter.dev/development/ui/navigation

 

Navigation and routing

Overview of Flutter's navigation and routing features

docs.flutter.dev

https://api.flutter.dev/flutter/widgets/Navigator-class.html

 

Navigator class - widgets library - Dart API

A widget that manages a set of child widgets with a stack discipline. Many apps have a navigator near the top of their widget hierarchy in order to display their logical history using an Overlay with the most recently visited pages visually on top of the o

api.flutter.dev

https://api.flutter.dev/flutter/widgets/Router-class.html

 

Router class - widgets library - Dart API

The dispatcher for opening and closing pages of an application. This widget listens for routing information from the operating system (e.g. an initial route provided on app startup, a new route obtained when an intent is received, or a notification that th

api.flutter.dev