반응형
1차 수정 (2023-10-13)
WillPopScope 버전 업데이트로 인한 마이그레이션 방법
Preview
이번 포스팅에서는... WillPopScope를 활용하여
- 앱 전반적인 모든 페이지에서 기능이 작동할 수 있도록 설정을 하고
- 뒤로가기 기능을 재정의(커스터마이징)하여 상황에 맞게 뒤로가기 기능을 활용
하는 것을 목표로 합니다.
포스팅에서 설명하는 소스 코드는 깃허브에서 확인 및 다운로드 가능합니다.
- 뒤로가기 버튼을 누르면 앱 종료 알림이 뜨고, 확인을 누를 경우 앱 종료
- 뒤로가기 버튼 한번을 누르면 하단에 알림이 뜨고, 다시 한번 누를 경우 앱 종료
WillPopScope
뒤로가기 버튼을 누를 때 WillPop에서 콜백함수를 호출하며, 반환값은 Future 타입을 반환
- 즉, 앱에서 뒤로가기 버튼(Back Button)을 누를 경우 작동되는 이벤트를 정의할 수 있도록 해준다.
사용하는 이유
사용하는 이유에 대한 간단한 2가지의 예시를 들어보자.
- a, b, c 페이지가 있고, 각각의 페이지에 뒤로가기 버튼을 해야 할 때
- 뒤로가기 버튼에 대한 기능 구현을 각각의 페이지에서 하면 너무 번거롭고 생산성도 떨어지게 된다.
- 하물며 페이지가 많으면 많을수록 구현해야하는 작업량도 많아지게 된다.
- 특정 페이지에서 정보 입력을 위해 양식을 작성중이거나, 중요한 작업을 진행할 수 있다.
- 뒤로가기 버튼을 누르게 되면 작업하던 정보를 잃게된다.
- 뒤로가기 버튼을 무력화하여 정보 손실을 최소화하는 것도 하나의 방법이 될 수 있다.
즉, 'WillPopScope' 를 사용하여 위젯을 감싸면 뒤로가기 버튼 이벤트 자체를 가로채서 WillPop에서 뒤로가기 버튼 이벤트를 재정의 할 수 있다.
구문
WillPopScope(child: child, onWillPop: onWillPop)
child
- WillPop을 이용하여 뒤로가기 버튼을 가로챌 위젯 나열
- 반드시 null이 아닌 위젯으로 정의되어 있어야 한다.
- 쉽게 말해서, Scaffold 위젯을 감싸게 되면 Scaffold 하위의 위젯들은 모두 뒤로가기 버튼에 대한 이벤트를 WillPop에서 공통적으로 다룰 수 있다.
onWillPop
- 뒤로가기 버튼에 대한 콜백함수를 정의
- 뒤로가기 버튼에 대한 기능 구현을 하는 영역을 의미한다.
구현 절차
- 위젯을 WillPopScope로 감싼다.
- onWillPop에 뒤로가기 버튼을 기능 구현할 콜백함수 코드를 작성한다.
- 반환값의 true : 현재 페이지에서 사용자의 의도대로 뒤로가기 동작을 수행한다.
- 반환값의 false : 뒤로가기 버튼의 기본 동작을 막고 현재 화면에 머무른다.
- 즉, 반환값이 true일 경우 뒤로가기 버튼의 원래의 기능을 수행한다. (앱 종료나 뒤로가기 기능)
- child에 감쌀 위젯을 정의한다.
@override
Widget build(BuildContext context) {
// 위젯을 감싸서 WillPop을 통해 뒤로가기 버튼 정의
return WillPopScope(
// 뒤로가기 버튼을 누를 경우의 이벤트 정의
onWillPop: () async {
// 뒤로가기 기능 구현 코드 작성
return false;
},
// 감쌀 위젯 정의
child: const Scaffold(
body: Center(
child: Text('Hello World!'),
),
),
);
}
예제 (Sample Code)
- 주석으로 표시되어 있는 코드 작성 부분에 포스팅에 기재되어 있는 함수 호출
- printMsg() : 메시지 콘솔에 출력
- onBackPressed(context) : 종료 다이얼로그 출력
- onBackDoublePressed(context) : 두 번 누를 경우 앱 종료
- 사용자 정의 함수이므로 상황에 맞게 변경하여서 사용
void main() {
runApp(
const MaterialApp(
home: MyApp(),
),
);
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> { @override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
/* 코드 작성 부분 */
return false;
},
child: const Scaffold(
body: Center(
child: Text('Hello World!'),
),
),
);
}
뒤로가기 버튼 클릭 시 콘솔에 메시지 출력
// printMsg();
// onWillPop 콜백 함수에서 콘솔에 메시지를 출력하는 함수
void printMsg() {
print('WillPopCallback : Hello World!');
}
- 뒤로가기 버튼 클릭 시
뒤로가기 버튼 클릭 시 종료 알림 다이얼로그 출력
// 앱 종료를 위해 SystemNavigator.pop() 기능을 사용하기 위한 라이브러리 추가
import 'package:flutter/services.dart';
// onBackPressed(context);
// 뒤로가기 버튼 누를 경우 종료 다이얼로그 출력 함수
Future<void> onBackPressed(BuildContext context) async {
await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Center(child: Text('앱을 종료하시겠습니까?')),
actions: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// 취소 버튼
ElevatedButton(
child: const Text('취소'),
onPressed: () => Navigator.pop(context),
),
// 종료 버튼
ElevatedButton(
child: const Text('확인'),
onPressed: () => SystemNavigator.pop(),
),
],
),
],
),
);
}
- 뒤로가기 버튼 클릭 시
- 취소 : 앱 화면으로 돌아오기
- 확인 : 앱 종료하기
뒤로가기 버튼 두 번 클릭 시 앱 종료
이 함수는 두 번 클릭확인을 위해 추가 코드를 작성하여야 합니다.
- 시간 확인을 위한 시계열 타입 전역 변수
- onWillPop 콜백 함수에서 bool 타입의 반환값을 받아 처리하는 예외처리 코드
또한, 뒤로가기 버튼을 누를 경우 사용자에게 알림을 위한 간단한 UI를 추가해봅시다.
/* 시간 확인을 위한 전역 변수 선언 */
// 뒤로가기 버튼의 누른 시간을 저장하기 위한 변수
DateTime? currentBackPressTime;
// onBackDoublePressed(context);
// 뒤로가기 버튼 두 번 누를 경우 앱 종료 함수
Future<bool> onBackDoublePressed(BuildContext context) async {
// 현재 시간 확인
DateTime now = DateTime.now();
// 뒤로가기 두 번 누를 경우의 종료 이벤트 정의
// currentBackPressTime : 뒤로가기 버튼을 누른 시간을 저장하고 2초 내에 다시 버튼이 눌리는지 확인하기 위한 변수
// 2초 후 뒤로가기 버튼 눌림 : if를 통한 반환값 : false (종료하지 않음)
// 2초 내 뒤로가기 버튼 눌림 : if를 통한 반환값 : true (앱 종료)
if (currentBackPressTime == null ||
now.difference(currentBackPressTime!) > const Duration(seconds: 2)) {
currentBackPressTime = now;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
const Icon(
Icons.notification_important,
color: Colors.white,
),
const SizedBox(width: 10),
Expanded(
child: Text(
'뒤로가기 버튼을 한 번 더 누르시면 종료됩니다.',
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(color: Colors.white),
),
),
],
),
),
);
// 처음 뒤로가기 버튼을 눌렀거나 2초 이후에 누를 경우 : false
return Future.value(false);
}
// 2초 이내에 뒤로가기 버튼을 또 눌럿을 경우 : true
return Future.value(true);
}
/* onWillPop 콜백 함 수 내의 코드에서 두 번 누를 경우의 예외 처리 추가 */
return WillPopScope(
onWillPop: () async {
// 뒤로가기 버튼 두 번 누를 경우 앱 종료
if (await onBackDoublePressed(context)) {
return true;
}
return false;
},
- 뒤로가기 버튼 한번 클릭 시
- 사용자에게 뒤로가기 두 번 누를 경우에 대한 알림 추가 (앱의 하단 확인)
- 뒤로가기 버튼을 한번 클릭 후, 2초 내에 다시 클릭 시
- 앱 종료
전체 소스 코드
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(
const MaterialApp(
home: MyApp(),
),
);
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// 뒤로가기 버튼의 누른 시간을 저장하기 위한 변수
DateTime? currentBackPressTime;
@override
Widget build(BuildContext context) {
// 위젯을 감싸서 WillPop을 통해 뒤로가기 버튼 정의
return WillPopScope(
// 뒤로가기 버튼을 누를 경우의 이벤트 정의
onWillPop: () async {
// 콘솔에 메시지 출력
//printMsg();
// 뒤로가기 버튼 누를 경우 앱 종료 확인 다이얼로그 출력
//onBackPressed(context);
// 뒤로가기 버튼 두 번 누를 경우 앱 종료
if (await onBackDoublePressed(context)) {
return true;
}
// 뒤로가기 버튼 기본 기능 무력화
return false;
},
// 감쌀 위젯 정의
child: const Scaffold(
body: Center(
child: Text('Hello World!'),
),
),
);
}
// onWillPop 콜백 함수에서 콘솔에 메시지를 출력하는 함수
void printMsg() {
print('WillPopCallback : Hello World!');
}
// 뒤로가기 버튼 누를 경우 종료 다이얼로그 출력 함수
Future<void> onBackPressed(BuildContext context) async {
await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Center(child: Text('앱을 종료하시겠습니까?')),
actions: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// 취소 버튼
ElevatedButton(
child: const Text('취소'),
onPressed: () => Navigator.pop(context),
),
// 종료 버튼
ElevatedButton(
child: const Text('확인'),
onPressed: () => SystemNavigator.pop(),
),
],
),
],
),
);
}
// 뒤로가기 버튼 두 번 누를 경우 앱 종료 함수
Future<bool> onBackDoublePressed(BuildContext context) async {
// 현재 시간 확인
DateTime now = DateTime.now();
// 뒤로가기 두 번 누를 경우의 종료 이벤트 정의
// currentBackPressTime : 뒤로가기 버튼을 누른 시간을 저장하고 2초 내에 다시 버튼이 눌리는지 확인하기 위한 변수
// 2초 후 뒤로가기 버튼 눌림 : if를 통한 반환값 : false (종료하지 않음)
// 2초 내 뒤로가기 버튼 눌림 : if를 통한 반환값 : true (앱 종료)
if (currentBackPressTime == null ||
now.difference(currentBackPressTime!) > const Duration(seconds: 2)) {
currentBackPressTime = now;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
const Icon(
Icons.notification_important,
color: Colors.white,
),
const SizedBox(width: 10),
Expanded(
child: Text(
'뒤로가기 버튼을 한 번 더 누르시면 종료됩니다.',
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(color: Colors.white),
),
),
],
),
),
);
// 처음 뒤로가기 버튼을 눌렀거나 2초 이후에 누를 경우 : false
return Future.value(false);
}
// 2초 이내에 뒤로가기 버튼을 또 눌럿을 경우 : true
return Future.value(true);
}
}
참고
반응형