Flutter AppBar와 Drawer 기본 설정 및 메뉴 이동

AppBar에 메뉴 아이콘을 그렸다는 사실과 Drawer가 열린다는 사실은 다릅니다. Drawer를 닫는 코드만 넣은 메뉴도 화면을 바꾸지는 않습니다. 이 글은 Scaffold에 상단 바와 옆 메뉴를 연결한 뒤, 버튼 동작·context 위치·본문 교체·상세 화면 이동을 각각 확인하는 예제를 다룹니다.

2026년 10월 9일 Windows의 Flutter 3.47.5·Dart 3.13.4 격리 프로젝트에서 네 위젯 테스트를 실행했습니다. flutter analyze는 오류 없이 끝났고 네 테스트가 통과했습니다. 빈 콜백, 잘못된 context, 같은 화면에서 본문 교체, 새 페이지에서 뒤로가기를 따로 확인했으며 실기기 캡처나 Material 3 NavigationDrawer 테스트는 아닙니다.

leading을 비워 둔 기본 연결부터 확인

Scaffold(
  appBar: AppBar(title: const Text('Home')),
  drawer: const Drawer(child: Text('Menu')),
  body: const Text('home-body'),
)

이번 테스트에서는 기본 AppBar에 DrawerButton이 한 개 나타났습니다. 버튼을 누르고 애니메이션이 끝난 뒤 ScaffoldState.isDrawerOpen이 true가 됐습니다. 메뉴 선택 후에는 닫힌 상태까지 검사했습니다. 버튼의 모양만 찾거나 Drawer 위젯이 트리에 있다는 사실만 확인하면 실제 열림을 놓칠 수 있습니다.

leading이 null이면 automaticallyImplyLeading이 자동 버튼 추론을 제어합니다. false로 바꾼 테스트에서는 자동 DrawerButton이 없었지만 Scaffold에는 Drawer가 그대로 있었고 openDrawer를 직접 호출하면 열렸습니다. 이 설정은 Drawer 자체를 없애거나 기능을 비활성화하는 옵션이 아닙니다.

Flutter Drawer 네 위젯 테스트의 자동 버튼 context 실패 Builder 수정 본문 교체와 뒤로가기 결과
2026년 10월 9일 공개 조회 또는 격리 환경의 실제 실행 결과를 정리한 설명 도식입니다. 실기기 오류 화면이 아닙니다.

직접 만든 메뉴 아이콘이 반응하지 않는 두 원인

테스트 조건 실행 관찰 수정 방향
leading 지정, onPressed는 빈 함수 아이콘을 눌러도 isDrawerOpen=false 자동 동작을 기대하지 말고 실제 openDrawer 연결
Scaffold를 반환하는 build의 context 사용 FlutterError Scaffold 아래의 context 사용
leading 안 Builder의 context 사용 Drawer가 열리고 추가 예외 없음 같은 조건으로 정상 동작 재검증

기존처럼 IconButton(onPressed: () {}, icon: Icon(Icons.menu))를 넣으면 버튼은 보이지만 콜백이 아무 일도 하지 않습니다. non-null leading을 직접 주면 automaticallyImplyLeading 옵션이 그 버튼의 행동을 만들어 주지 않습니다. 장식용 예제를 실제 메뉴 동작처럼 읽지 않도록 콜백까지 포함합니다.

두 번째 실패에서는 현재 State의 build(context)가 새 Scaffold를 반환하는 구조였습니다. 그 context의 조상에는 반환할 Scaffold가 아직 포함되지 않으므로 Scaffold.of(context)가 찾지 못했습니다. 실제 오류의 첫 줄은 Scaffold.of() called with a context that does not contain a Scaffold.였습니다. 테스트는 의도한 오류를 수집한 뒤 Builder 버전에서 예외가 없는지 확인했습니다.

leading: Builder(
  builder: (inner) => IconButton(
    tooltip: 'Open menu',
    icon: const Icon(Icons.menu),
    onPressed: () => Scaffold.of(inner).openDrawer(),
  ),
),

Builder는 UI를 꾸미는 용도가 아니라 해당 위치의 하위 BuildContext를 얻는 데 사용했습니다. context라는 변수명을 inner로 바꾼다고 해결되는 것이 아니라 실제 트리 위치가 달라져야 합니다. 중첩 Scaffold에서는 가장 가까운 조상 Scaffold가 선택되므로 “찾기만 하면 올바른 메뉴다”라고 가정하지 않습니다.

최상위 메뉴는 본문 교체인지 새 route인지 먼저 정한다

저장 목록 같은 최상위 목적지로 바꿀 때는 이번 예제에서 선택 인덱스를 State 필드에 저장하고 본문을 교체했습니다. 메뉴를 누른 뒤 selected=1, saved-body 표시, Drawer 닫힘을 확인했습니다. Navigator.canPop은 false여서 새 상세 route가 쌓이지 않았습니다. 단순히 Navigator.pop만 호출한 기존 예제와 달리 실제 내용이 바뀌는지를 검사했습니다.

// Drawer 아래 Builder에서 받은 drawerContext
onTap: () {
  Scaffold.of(drawerContext).closeDrawer();
  setState(() => selected = 1);
},

closeDrawer는 메뉴 패널 닫기, setState는 선택 상태와 본문 갱신이라는 별도 역할입니다. 선택 항목 강조, AppBar 제목, body가 같은 상태를 사용하도록 합니다. 화면마다 페이지를 계속 push하면 최상위 메뉴를 몇 번 고른 뒤 뒤로가기가 메뉴 선택 이력을 따라가는 다른 UX가 됩니다. 반대로 사용자가 이전 상세 화면으로 돌아가야 한다면 단순 인덱스 교체는 그 요구를 만족하지 않습니다.

상세 화면은 Drawer를 닫고 push한 뒤 돌아오는 것까지 테스트

onTap: () {
  final navigator = Navigator.of(drawerContext);
  Scaffold.of(drawerContext).closeDrawer();
  navigator.push(MaterialPageRoute<void>(
    builder: (_) => Scaffold(
      appBar: AppBar(title: const Text('Detail')),
      body: const Text('detail-body'),
    ),
  ));
},

이 테스트는 하나의 MaterialApp Navigator만 사용했습니다. 상세 body와 BackButton이 나타났고, 뒤로가기 버튼을 누르면 home-body가 다시 보였습니다. 돌아온 Scaffold의 isDrawerOpen은 false였습니다. 열린 메뉴 위에서 새 route만 push해 뒤로갔을 때 이전 메뉴가 다시 드러나는 상태를 막고 싶은 경우 이 확인이 필요합니다.

Navigator 참조는 메뉴 context에서 먼저 얻고, 그 뒤 메뉴를 닫았습니다. 이 코드에는 await가 없으며 화면 전환 전에 서버 요청을 기다리는 코드는 테스트하지 않았습니다. 비동기 작업 뒤 context를 쓰는 경우에는 mounted와 취소 조건을 별도로 검토합니다. 중첩 Navigator나 선언형 라우터를 쓰면 어느 stack을 대상으로 이동할지도 명확히 해야 합니다.

버튼·패널·내용·뒤로가기 네 가지 합격선을 남긴다

관찰 지점 이번 합격 결과 빠뜨리기 쉬운 실패
메뉴 버튼 기본 버튼 1개, 직접 버튼은 콜백 연결 아이콘은 있지만 빈 함수
패널 상태 열기 true, 선택 후 false 다른 Scaffold의 메뉴 제어
목적지 저장 본문 또는 상세 body 확인 닫기만 하고 실제 이동 없음
되돌아오기 홈 복귀와 닫힌 Drawer 확인 메뉴 선택마다 route 중복 적재

테스트에서는 pumpAndSettle 뒤 상태를 읽어 애니메이션 도중 값과 최종 값을 구분했습니다. 탭 직후 스크린샷 한 장만으로 닫힘을 판단하지 않습니다. 반복 탭 방지·키보드 및 스크린리더 접근성·폭이 좁은 화면·긴 메뉴 스크롤은 이번 네 테스트에 포함하지 않았고 실제 앱의 추가 합격 조건입니다. 메뉴 버튼에는 의미 있는 tooltip을 제공하고 접근성 검증을 별도로 수행합니다.

재현 범위와 Material 3 적용 경계

flutter analyze
flutter test --reporter expanded
// No issues found
// 4 tests passed: automatic, hidden/inert, context, push/back

기본 Drawer 동작을 검증했다고 NavigationDrawer의 선택 상태와 목적지 처리까지 같다고 보지 않습니다. 공식 Drawer 문서는 Material 3 구성에서 NavigationDrawer를 선호한다고 안내하지만, 전환 시에는 목적지 이벤트와 selectedIndex 처리를 해당 API로 다시 검증해야 합니다. 이번 결과는 Flutter 3.47.5 위젯 테스트와 한 Navigator 구조에만 해당하며 실기기·endDrawer·중첩 stack·상태 복원을 실행하지 않았습니다.

본문 선택값을 어디에 저장해야 하는지는 State 유지와 재빌드 실험에서 이어집니다. 하단 탭 UI와는 메뉴 열기·닫기 UX가 다르므로 이 글의 route 검증을 그대로 복사하지 않습니다.

실험 동작은 2026년 10월 9일 자동 leading 조건, Scaffold.of의 context 탐색, closeDrawer, Drawer와 NavigationDrawer 차이 공식 API와 대조했습니다.