flutter

[플러터]Flutter / PopScope를 이용한 뒤로가기 만들기

ch5c 2024. 9. 11. 19:43

저번 포스팅에서는 앱이 시작되면 나오는 인트로를 만들어봤다. 이번에는 PopScope를 사용해서 뒤로 가기를 눌렀을 때 다이얼로그가 호출되고 거기서 이제 나갈지 말지 정하는 기능을 구현해볼 것이다.

일단 시작하기에 앞서 PopScope 위젯을 먼저 알아보자.

 

PopSocpe 위젯이란?

PopScope 위젯은 flutter 에서 뒤로 가기 기능을 구현할 때 사용하는 위젯으로 예전에는 WillPopScope를 사용했지만 이제는 공식문서에서도 PopScope를 사용하라고 권고하고 있다.ㅡ

 

flutter 공식 문서에 기재되어 있는 내용

 

암튼 간에 이 PopScope 위젯은 하위 속성으로

canPop, onPopInvokedWithResult, onPopInvoked, child 를 가지고 있다. 

 

하나씩 알아보도록 하자.

 

canPop
canPop: false // false 일때는 뒤로가기 금지, true일땐 허용

 

canPop은 사용자가 뒤로 가기를 눌렀을 때 뒤로 가질 건지, 가지 않을건지를 설정할 수 있는 값이다.

canPop은 불리언 값을 기본적으로 가지며 false일때엔 뒤로 가기를 눌러도 뒤로 가지지 않고 true 일 때는 그냥 뒤로 가기를 누르면 정상적으로 뒤로 가진다. 

 

onPopInvokedWithResult
onPopInvokedWithResult: (didPop, result) async {}

 

onPopInvokedWithResult는 뒤로가기를 누른 것을 감지하는 값이다. 인자값으로는 (didPop, result) 을 받는다.

이제 {} 안에다가는 다이얼로그를 호출하는 코드를 작성하거나 마음대로 여러 가지 코드를 넣어주면 되겠다.

 

onPopInvoked

 

onPopInvoked는 flutter에서 이제 사용하지 말라고 권고하는 값이다. 그냥 이런 게 있다고 알아만 두자.

 

child

 

어떻게 보면 당연한거겠지만 PopScopechild 값을 가진다. child 값안에는 자신이 만든 빌드 위젯들을 넣어주면 되겠다.

 

그럼 이제 기본 속성들을 알아보았으니 실제로 만들어 보자.

 

PopScope를 활용한 뒤로가기 위젯

 

전체 코드

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

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

  @override
  State<PopScopeWidget> createState() => _PopScopeWidgetState();
}

class _PopScopeWidgetState extends State<PopScopeWidget> {

  bool isDialog = true;

  @override
  Widget build(BuildContext context) {
    return PopScope(
      canPop: false,
      onPopInvokedWithResult: (didPop, result) async {
        if (isDialog) {
          isDialog = false;
          showExitDialog(context).then((value) {
            isDialog = true;
          },);
        }
      },
      child: Scaffold(
        body: Center(
          child: Text('뒤로가기를 눌러보세요'),
        ),
      ),
    );
  }

  Future<void> showExitDialog(BuildContext context) async {
    final shouldExit = await showDialog(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: Text('앱을 종료하시겠습니까?'),
          content: Text('앱을 나가시겠습니까?'),
          actions: [
            TextButton(
                onPressed: () {
                  Navigator.of(context).pop(false);
                },
                child: Text('머무르기')
            ),
            TextButton(
              onPressed: () {
                Navigator.of(context).pop(true);
              },
              child: Text('나가기'),
            )
          ],
        );
      },
    );
    if (shouldExit == true) {
      SystemNavigator.pop();
    }
  }
}

 

이 코드에서 봐야 할 것은 크게 두 가지다. 

PopScope

 

팝스코프 위젯은 뒤로가기와 관련된 동작을 하게 해주는 위젯으로 이번 코드에서 필수적인 요소다.

 showExitDialog

 

뒤로 가기를 눌렀을 때 호출되는 함수로 다이얼로그를 호출하고 뒤로 갈지 정하게 해 준다.

 

그럼 한번 코드를 봐보자.

return PopScope(
      canPop: false,
      onPopInvokedWithResult: (didPop, result) async {
        if (isDialog) {
          isDialog = false;
          showExitDialog(context).then((value) {
            isDialog = true;
          },);
        }
      },
      child: Scaffold(
        body: Center(
          child: Text('뒤로가기를 눌러보세요'),
        ),
      ),
    );

 

먼저 canPopfalse로 설정해 놨다. false로 해놨기 때문에 뒤로 가기를 눌러도 뒤로가지는 일은 없다. 그리고 이제 여기서 onPopInvokedWithResult를 사용해서 뒤로가기 버튼을 누르는 것을 감지를 한다. 

bool isDialog = true;

 

이제 위에서 선언해 놨던 불리언 값을 사용한다. if (isDialog) 로 현재 isDialog의 불리언 값이 true인지 확인한다.

만약 true라면 다음에도 뒤로 가기를 눌렀을 때 실행할 수 있게 false로 바꿔준다. (불리언 값을 선언한 것과 이런 이프문을 넣는 게 귀찮을 수 있지만 꼭 필요한 코드다. 이게 없으면 뒤로 가기를 눌렀을 때 막 계속 호출되거나 그렇게 된다.)

 

그런 다음 showExitDialog에 컨텍스트 값을 담아 호출해 준다. 그리고 .then을 사용해서 showExitDialog 끝난 후에 불리언 값을 true로 바꿔준다.

 

그럼 이제 showExitDialog  함수를 살펴보자.

Future<void> showExitDialog(BuildContext context) async {
  final shouldExit = await showDialog(
    context: context,
    builder: (context) {
      return AlertDialog(
        title: Text('앱을 종료하시겠습니까?'),
        content: Text('앱을 나가시겠습니까?'),
        actions: [
          TextButton(
              onPressed: () {
                Navigator.of(context).pop(false);
              },
              child: Text('머무르기')
          ),
          TextButton(
            onPressed: () {
              Navigator.of(context).pop(true);
            },
            child: Text('나가기'),
          )
        ],
      );
    },
  );
  if (shouldExit == true) {
    SystemNavigator.pop();
  }
}

 

코드를 보면 알겠지만 showExitDialog  은 내가 직접 만든 함수기 때문에 아무 이름으로 해도 된다. 허나 밑에 있는 showDialog 와 같은 경우는 제공해 주는 것이기 때문에 이름 그대로 사용해야 한다.

 

showDialog는 제네릭 타입으로 boolString 등을 가진다. 하지만 이 코드에서는 불리언으로 사용되고 있다. 보기에 불편하다 하면 showDialog<bool>로 사용해도 이와 똑같이 동작하기 때문에 상관없다. 암튼 그래서 final shouldExit 라는 값을 하나 선언 해 주고 거기 안에다가 await로 코드가 끝날 때까지 기다려 주고 showDialog 를 선언해 줬다. 이렇게 함으로써 showDialog가 끝난 후 뒤로 가는 동작을 할 수 있게 해 준다. 

 

먼저 showDialog는 하위 값으로 contextbuilder 값을 가진다. 빌더에는 우리가 실행해 줄 AlertDialog를 리턴해 줄 것이다. 

 

AlertDialog 안에 title 값으로 가장 큰 제목을 입력해 주고 content 값으로 부제를 입력해 준다. 그런 다음 하위속성으로 actions 값을 가지는데 여기다가 선택지를 만들어 놓으면 된다. TextButton을 사용해서 만들었고 누르면 내비게이터 팝으로 다이얼로그를 닫히게 했다. 팝에 false를 인자값으로 가지고 가게 한다. 두번째 TextButton도 네비게이터 팝으로 다이얼로그를 닫히게 하고 팝에 true를 인자값으로 가지게 가고 했다.

if (shouldExit == true) {
  SystemNavigator.pop();
}

 

그렇게 다이얼로그가 닫히면 이제 showDialog 바깥에 있는 if 문이 실행된다. shouldExittrue인지 확인하는 if문인데 맨 처음에 파이널로 shoulExit 를 선언하고 그 안에다가 showDialog를 만든 것도 이렇게 사용하기 위함이다.

 

이것으로 자신이 누른 선택지에 따라 받아오는 인자값이 달라지게 된다. "머무르기"를 선택했다면 false를 받아오기 때문에 if문을 그냥 지나치게 되고 "나가기"를 선택했다면 true이기 때문에 if문으로 들어와서 SystemNavigator.pop();을 실행하게 된다.

 

시스템 내비게이터 팝이 많이들 사용하는 기존 내비게이터 팝과 비슷하다고 생각하면 오산이다. 내비게이터 팝은 전 화면으로 돌아가는 것이라면 시스템 내비게이터 팝은 그냥 아예 앱을 종료해 버리는 코드다.

 

이렇게 간단하게 PopScope를 사용해서 뒤로가기 로직을 만들어봤다. 많은 도움이 됬길 바라며 이번 포스팅 마치겠다.