Navigator 기초 — push, pop, Named Routes

앱에서 화면 전환은 필수입니다. Flutter는 Navigator를 사용해 스택 기반으로 화면을 관리합니다. 웹의 브라우저 히스토리와 비슷한 개념입니다.


Navigator의 동작 원리

Navigator는 화면(Route)을 스택 으로 관리합니다.

PLAINTEXT
push → 새 화면을 스택 위에 쌓기
pop  → 현재 화면을 스택에서 제거 (이전 화면으로 돌아감)

┌─────────┐
│  C 화면  │ ← 현재 화면 (최상단)
├─────────┤
│  B 화면  │
├─────────┤
│  A 화면  │ ← 루트 화면
└─────────┘

기본 push/pop

새 화면으로 이동 (push)

DART
// 홈 화면
class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('홈')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // 새 화면으로 이동
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => const DetailScreen(),
              ),
            );
          },
          child: const Text('상세 화면으로'),
        ),
      ),
    );
  }
}

이전 화면으로 돌아가기 (pop)

DART
class DetailScreen extends StatelessWidget {
  const DetailScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('상세')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.pop(context);  // 이전 화면으로
          },
          child: const Text('뒤로 가기'),
        ),
      ),
    );
  }
}

데이터 전달

다음 화면으로 데이터 넘기기

DART
// 생성자로 전달
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => DetailScreen(
      title: '게시물 제목',
      id: 42,
    ),
  ),
);

// 받는 쪽
class DetailScreen extends StatelessWidget {
  final String title;
  final int id;

  const DetailScreen({
    super.key,
    required this.title,
    required this.id,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: Center(child: Text('ID: $id')),
    );
  }
}

이전 화면으로 결과 돌려주기

DART
// A 화면: 결과를 기다림
final result = await Navigator.push<String>(
  context,
  MaterialPageRoute(
    builder: (context) => const SelectionScreen(),
  ),
);

if (result != null) {
  print('선택한 값: $result');
}

// B 화면: 결과를 돌려줌
Navigator.pop(context, '선택된 아이템');

Named Routes

라우트에 이름을 붙여서 관리하는 방식입니다.

DART
// MaterialApp에서 라우트 정의
MaterialApp(
  initialRoute: '/',
  routes: {
    '/': (context) => const HomeScreen(),
    '/detail': (context) => const DetailScreen(),
    '/settings': (context) => const SettingsScreen(),
    '/profile': (context) => const ProfileScreen(),
  },
)

// 이름으로 이동
Navigator.pushNamed(context, '/detail');

// 인자 전달
Navigator.pushNamed(
  context,
  '/detail',
  arguments: {'id': 42, 'title': '제목'},
);

// 인자 받기
final args = ModalRoute.of(context)!.settings.arguments
    as Map<String, dynamic>;

onGenerateRoute (동적 라우팅)

DART
MaterialApp(
  onGenerateRoute: (settings) {
    // '/detail/42' 같은 동적 경로 처리
    if (settings.name?.startsWith('/detail/') ?? false) {
      final id = settings.name!.split('/').last;
      return MaterialPageRoute(
        builder: (context) => DetailScreen(id: int.parse(id)),
      );
    }

    // 404 처리
    return MaterialPageRoute(
      builder: (context) => const NotFoundScreen(),
    );
  },
)

Navigator 2.0 vs 1.0

구분Navigator 1.0Navigator 2.0
방식명령형 (push/pop)선언적
딥링크제한적완전 지원
웹 URL제한적완전 지원
복잡도낮음높음

실무에서는 Navigator 2.0을 직접 구현하기보다 GoRouter 같은 라우팅 패키지를 사용하는 것이 일반적입니다. 이 내용은 별도의 글에서 다루겠습니다.


유용한 Navigator 메서드

DART
// pushReplacement: 현재 화면을 교체 (로그인 → 홈)
Navigator.pushReplacement(
  context,
  MaterialPageRoute(builder: (context) => const HomeScreen()),
);

// pushAndRemoveUntil: 스택을 정리하며 이동 (로그아웃)
Navigator.pushAndRemoveUntil(
  context,
  MaterialPageRoute(builder: (context) => const LoginScreen()),
  (route) => false,  // 모든 이전 라우트 제거
);

// popUntil: 특정 화면까지 돌아가기
Navigator.popUntil(context, (route) => route.isFirst);

// canPop: 뒤로 갈 수 있는지 확인
if (Navigator.canPop(context)) {
  Navigator.pop(context);
}

// maybePop: 가능하면 pop, 아니면 무시
Navigator.maybePop(context);

WillPopScope — 뒤로가기 가로채기

DART
// 뒤로가기 시 확인 다이얼로그 표시
PopScope(
  canPop: false,
  onPopInvokedWithResult: (didPop, result) async {
    if (didPop) return;

    final shouldPop = await showDialog<bool>(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('나가시겠습니까?'),
        content: const Text('변경사항이 저장되지 않습니다.'),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context, false),
            child: const Text('취소'),
          ),
          TextButton(
            onPressed: () => Navigator.pop(context, true),
            child: const Text('나가기'),
          ),
        ],
      ),
    );

    if (shouldPop == true && context.mounted) {
      Navigator.pop(context);
    }
  },
  child: Scaffold(
    appBar: AppBar(title: const Text('편집')),
    body: const Text('폼 내용'),
  ),
)

정리

  • Navigator는 스택 기반으로 화면을 관리합니다 (push/pop)
  • await Navigator.push()로 다음 화면에서 결과를 받을 수 있습니다
  • Named Routes로 문자열 기반 라우팅이 가능하지만, 타입 안전성이 부족합니다
  • pushReplacement는 화면 교체, pushAndRemoveUntil은 스택 초기화에 사용합니다
  • 실무에서는 GoRouter 같은 패키지를 많이 사용합니다
댓글 로딩 중...