[ 개발 환경 ]

 

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();
              });
            },
          ),
        ],
      ),
    );
  }
}

 

 

 

 

 

 

 

 

+ Recent posts