[ 개발 환경 ]
Flutter (Channel stable, 3.24.0, on macOS 14.5 23F79 darwin-arm64, locale ko-KR)
Dart SDK version: 3.5.0 (stable)
[ 문제 상황 ]
- WillPopScope & PopScope 비정상 작동 확인
[ 문제 원인 ]
Deprecated될 예정 혹은 이미 되었거나 앱이 종료되는 것과 관련된 작업 수행 과정에서 해당 소스코드 사용시 적용이 안되는 문제가 있음
[ 해결 방법 ]
1. back_button_interceptor 설치
flutter pub add back_button_interceptor
2. DeviceBackButtonHandler 구현해 앱 종료에 대한 "뒤로가기" 시도인지 단순히 앱 내에서 이전 화면으로 돌아가는 것에 대한 "뒤로가기" 시도인지 판단해 수행
3. DeviceBackButtonHandler 내의 bool _isEnabled 변수값 수정으로 판단
// device_back_button_handler.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:back_button_interceptor/back_button_interceptor.dart';
class DeviceBackButtonHandler {
static bool _isDialogShowing = false;
static bool _isEnabled = true;
static void addInterceptor(BuildContext context) {
BackButtonInterceptor.add((bool stopDefaultButtonEvent, RouteInfo info) {
if (!_isEnabled) {
return false; // 비활성화된 경우 기본 동작을 실행합니다.
}
if (_isDialogShowing) {
Navigator.of(context).pop(); // 다이얼로그 닫기
} else {
_showExitDialog(context);
}
return true; // 뒤로가기 버튼의 기본 동작을 막습니다.
});
}
static void removeInterceptor() {
BackButtonInterceptor.remove((bool stopDefaultButtonEvent, RouteInfo info) {
return true;
});
}
static Future<void> _showExitDialog(BuildContext context) async {
_isDialogShowing = true;
await showDialog<void>(
context: context,
barrierDismissible: true, // 다이얼로그 밖을 터치해도 닫히도록 설정
builder: (BuildContext context) {
return AlertDialog(
title: const Text('앱 종료'),
content: const SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text('앱을 종료하시겠습니까?'),
],
),
),
actions: <Widget>[
TextButton(
child: const Text('아니오'),
onPressed: () {
Navigator.of(context).pop(); // 다이얼로그만 닫기
},
),
TextButton(
child: const Text('예'),
onPressed: () {
Navigator.of(context).pop(); // 다이얼로그 닫고
SystemNavigator.pop(); // 앱 종료
},
),
],
);
},
);
_isDialogShowing = false;
}
static void enable() {
_isEnabled = true;
}
static void disable() {
_isEnabled = false;
}
}
4. App 실행시 나오는 첫 화면에서 DeviceBackButtonHandler의 interceptor 실행
// main_screen.dart
import 'package:flutter/material.dart';
import '../handler/device_back_button_handler.dart';
class MainScreen extends StatefulWidget {
const MainScreen({super.key});
@override
State<MainScreen> createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
int _selectedTabIndex = 0;
@override
void initState() {
super.initState();
DeviceBackButtonHandler.addInterceptor(context);
}
@override
void dispose() {
DeviceBackButtonHandler.removeInterceptor();
super.dispose();
}
@override
Widget build(BuildContext context) {
final ColorScheme currentTheme = Theme.of(context).colorScheme;
return Scaffold(
// ...
);
}
}
5. 뒤로가기 시도가 앱 종료 시도와 달라지는 화면 전환 버튼 onTap에 DeviceBackButtonHandler를 disable처리하고 다시 해당 화면 복귀시 DeviceBackButtonHandler를 enable 처리하는 소스코드 적용
// setting_main_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../default/handler/device_back_button_handler.dart';
import 'ticker_setting_screen.dart';
import 'exchange_setting_screen.dart';
class SettingMainScreen extends ConsumerStatefulWidget {
const SettingMainScreen({super.key});
@override
ConsumerState<SettingMainScreen> createState() => _SettingMainScreenState();
}
class _SettingMainScreenState extends ConsumerState<SettingMainScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: [
ListTile(
leading: const Icon(Icons.candlestick_chart),
trailing: const Icon(Icons.arrow_forward_ios),
title: const Text('Ticker Settings'),
onTap: () {
DeviceBackButtonHandler.disable();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const TickerSettingScreen(),
),
).then((_) {
DeviceBackButtonHandler.enable();
});
},
),
ListTile(
leading: const Icon(Icons.currency_exchange),
trailing: const Icon(Icons.arrow_forward_ios),
title: const Text('Exchange Settings'),
onTap: () {
DeviceBackButtonHandler.disable();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ExchangeSettingScreen(),
),
).then((_) {
DeviceBackButtonHandler.enable();
});
},
),
],
),
);
}
}