WillPopScope 클래스 - Flutter

Shawn Kang·2022년 7월 13일
1

Flutter

목록 보기
1/4
post-thumbnail

개요

하단 네비게이션 바에 대해 파던 중에, WillPopScope 클래스에 대해 알게 되었다. 이게 대체 뭐 하는 클래스인지 도저히 감이 안 와서 개인적으로 정리하기 위해 오랜만에 velog에 글을 써 본다.

하는 일?

WillPopScope 클래스 - Flutter 공식 API 문서에 따르면, WillPopScope 클래스는 이런 일을 한다고 한다:

Registers a callback to veto attempts by the user to dismiss the enclosing ModalRoute.

또 모르는 용어가 나왔다. ModalRoute는 뭘까. 공식 API 문서에서는 이런 클래스라고 한다:

A route that blocks interaction with previous routes.
ModalRoutes cover the entire Navigator.

대충 알아보니 Flutter에서는 Navigator 클래스로 스택을 통해 애플리케이션의 페이지들을 관리하는 것 같다. 앱을 사용하다 보면 메인 화면에서 버튼을 눌러 다음 화면으로 넘어가고, 이러면 여러 페이지들 간에 Depth가 누적이 되니까.

이 때 Navigator 클래스가 Flutter의 페이지 간 기본적인 Route 방식이라면, 이를 대체할 수 있는 다른 수단으로 PopupRoute, ModalRoute, PageRoute 등이 존재하는 것이다.

어쨌든 상당히 정확하지 않은 개인적인 결론은, ModalRoute에서 정해진 Route 관련 내용을 무시하고, 사용자가 직접 뒤로 가기 동작에 대한 Callback 함수를 지정할 수 있게 도와주는 클래스인 것 같다.

어떻게 동작하나요?

간단히 설명해보자면, WillPopScope는 그냥 기본적인 위젯에 뒤로 가기 동작에 대한 커스터마이징을 제공해준다고 생각하면 될 것 같다. 그래서 기본적으로 표시될 위젯을 child 매개변수로 가지고 있다. 이 child 위젯을 WillPopScope 클래스로 감싸는 느낌.

그리고 뒤로 가기 동작이 감지되면, onWillPop에서 정의된 async 함수를 Callback한다. 만약 onWillPop 함수가 true를 반환할 경우, 현재 화면이 Pop된다. 그렇지 않을 경우, 현재 화면은 그대로 유지된다.

다시 말해서 onWillPop 함수가 true를 반환하지 않을 경우, 뒤로 가기 버튼을 눌렀을 때 텍스트를 표시하는 등 다양한 동작으로 커스터마이징이 가능하다는 뜻.

생성자를 한 번 보자.

WillPopScope({
	Key? key,
    required Widget child,
    required WillPopCallback? onWillPop
})
  • Widget child: 기본적으로 표시될 위젯
  • WillPopCallback onWillPop: 뒤로 가기 동작이 감지될 시 실행될 함수

예제

방금 본 동작 방식에 관한 내용을 생각하면서, 공식 문서의 예제 코드를 보자.

기본 코드

import 'package:flutter/material.dart';

// 메인 함수
void main() => runApp(const MyApp());

// 주 위젯
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  static const String _title = 'Flutter Code Sample';

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: _title,
      home: MyStatefulWidget(),
    );
  }
}

// 주 위젯에 탑재될 Stateful 위젯
class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({Key? key}) : super(key: key);

  
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

여기까지는 Flutter를 어느 정도 만져봤다면 다 아는 내용이니까 넘어가겠다.

MyStatefulWidget의 State

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  bool shouldPop = true;
  
  
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        return shouldPop;
      },
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Flutter WillPopScope demo'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              OutlinedButton(
                child: const Text('Push'),
                onPressed: () {
                  Navigator.of(context).push<void>(
                    MaterialPageRoute<void>(
                      builder: (BuildContext context) {
                        return const MyStatefulWidget();
                      },
                    ),
                  );
                },
              ),
              OutlinedButton(
                child: Text('shouldPop: $shouldPop'),
                onPressed: () {
                  setState(
                    () {
                      shouldPop = !shouldPop;
                    },
                  );
                },
              ),
              const Text('Push to a new screen, then tap on shouldPop '
                  'button to toggle its value. Press the back '
                  'button in the appBar to check its behavior '
                  'for different values of shouldPop'),
            ],
          ),
        ),
      ),
    );
  }
}

위는 전체 코드고, 너무 기니까 부분적으로 나누어 보도록 하자.

onWillPop 함수

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  bool shouldPop = true;
  
  
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        return shouldPop;
      },

onWillPop 함수는 변수 shouldPop을 반환한다. shouldPopbool 자료형이니까 상황에 따라 false가 될 수도 있다는 말.

Push 버튼

OutlinedButton(
	child: const Text('Push'),
	onPressed: () {
		Navigator.of(context).push<void>(
			MaterialPageRoute<void>(
				builder: (BuildContext context) {
					return const MyStatefulWidget();
				},
			),
		);
	},
)

이 버튼을 누르면 현재 페이지의 복제본이 그대로 Navigation 스택에 Push 된다. 10번 누르면 스택에 같은 페이지가 10개 쌓인다는 말. 이 상태에서는 뒤로 가기 버튼을 10번 눌러야 앱을 끌 수 있을 것이다.

어쨌든 중요한 건, 버튼을 눌렀을 때 작동하는 onPressed 함수에 Route 관련 동작이 정의되어 있다는 것. 만약 위에서 언급한 onWillPop이 true를 return하지 않는다면 어떻게 될까?

shouldPop 버튼

OutlinedButton(
  child: Text('shouldPop: $shouldPop'),
    onPressed: () {
      setState(
      () {
        shouldPop = !shouldPop;
      },
    );
  },
),

이 버튼은 onWillPop 함수가 반환하는 bool 값을 반전시키는 버튼이다. 만약 현재 shouldPop 값이 true일 경우, 이 버튼을 누르면 false가 된다는 뜻.

만약 버튼을 누르게 되면 onWillPop 함수가 true가 아닌 값을 반환하므로, 아까 Push 버튼에서 정의한 onPressed 함수 내의 Route 동작은 일어나지 않게 된다.

마무리

실행 결과가 궁금한 분들은 WillPopScope 클래스 - Flutter 공식 API 문서에 들러 직접 돌려보도록 하자.
다음에 할 것도 생겼다. ModalRoute가 뭔지 좀 정확히 이해할 수 있었으면 좋겠다.

profile
i meant to be

0개의 댓글