Flutter 플랫폼 종속 라이프사이클 및 앱 상태 전환 관리

Flutter에서 개별 위젯의 라이프사이클과 달리, 애플리케이션 전체의 플랫폼 종속 라이프사이클은 운영체제의 상태 전환(예: Android의 백그라운드 전환, iOS의 제어 센터 호출 등)에 따라 발생합니다. 예를 들어, 동영상을 재생하는 앱이 사용자에 의해 홈 화면으로 이동하거나 다른 앱으로 전환될 때, 리소스 절약을 위해 재생을 일시 중지해야 하는 경우가 있습니다. 이러한 앱 수준의 상태 변화를 감지하고 처리하기 위해 Flutter는 AppLifecycleStateWidgetsBindingObserver를 제공합니다.

라이프사이클 상태 감지 구현

앱의 상태 변화를 감지하려면 StatefulWidgetState 클래스에 WidgetsBindingObserver 믹스인을 추가하고, initState에서 옵저버를 등록해야 합니다.

class PlatformLifecycleHandler extends StatefulWidget {
  const PlatformLifecycleHandler({Key? key}) : super(key: key);

  @override
  State<PlatformLifecycleHandler> createState() => _PlatformLifecycleHandlerState();
}

class _PlatformLifecycleHandlerState extends State<PlatformLifecycleHandler> 
    with WidgetsBindingObserver {
  
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    switch (state) {
      case AppLifecycleState.resumed:
        _handleResume();
        break;
      case AppLifecycleState.paused:
        _handlePause();
        break;
      case AppLifecycleState.inactive:
      case AppLifecycleState.detached:
      default:
        break;
    }
  }

  void _handleResume() {
    // 포그라운드 복귀 시 로직 (예: 동영상 재생 재개)
  }

  void _handlePause() {
    // 백그라운드 전환 시 로직 (예: 동영상 일시 중지)
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(child: Text('Lifecycle Observer')),
    );
  }
}

핵심은 didChangeAppLifecycleState 메서드를 오버라이드하여 상태 전환에 따른 로직을 구현하는 것입니다. AppLifecycleState는 다음과 같은 상태를 포함합니다.

AppLifecycleState 상태 상세

  • resumed: 애플리케이션이 화면에 표시되며 사용자의 입력에 응답할 수 있는 포그라운드 상태입니다.
  • inactive: 애플리케이션이 비활성화되어 사용자의 입력에 응답하지 않는 상태입니다. iOS에서는 전화 수신, Touch ID/Face ID 요청, 앱 전환기 또는 제어 센터 진입 시 발생합니다. Android에서는 분할 화면 모드, 전화 수신, 시스템 대화 상자 표시 등의 상황에서 이 상태로 전환됩니다.
  • paused: 애플리케이션이 화면에 표시되지 않고 백그라운드에서 실행되는 상태입니다. 이 상태에서는 Flutter 엔진이 Window.onBeginFrameWindow.onDrawFrame을 호출하지 않아 렌더링이 중단됩니다.
  • detached: 애플리케이션이 Flutter 엔진에 여전히 호스팅되고 있지만 플랫폼의 뷰(View)와는 분리된 상태입니다. 엔진이 처음 로드되어 플랫폼 뷰에 연결되는 과정이나, 네비게이션 스택에서 뷰가 파괴될 때 발생합니다.

주의사항: didChangeAppLifecycleState 콜백은 운영체제의 시스템 알림에 기반합니다. 따라서 사용자가 앱을 강제로 종료하거나, 기기 배터리 방전으로 전원이 꺼지는 등의 비정상적인 상황에서는 해당 콜백이 호출되지 않을 수 있습니다.

라우트(페이지) 간 전환과 라이프사이클 오해

개발자들이 자주 겪는 문제 중 하나는 페이지 A에서 페이지 B로 이동한 후, 다시 페이지 A로 돌아올 때 didChangeAppLifecycleState가 호출되지 않는 것입니다.

이는 didChangeAppLifecycleState가 Flutter의 내부 라우트(페이지) 전환이 아닌, 애플리케이션 전체의 운영체제 수준 상태 변화만을 감지하기 때문입니다. Android의 onResume과 유사한 동작을 페이지 단위로 구현하려면 라우팅 메커니즘을 활용해야 합니다.

페이지 B에서 페이지 A로 돌아올 때 데이터를 새로고침하려면, Navigator.push가 반환하는 Futureawait하여 결과를 처리하는 방식을 사용합니다.

class FirstScreen extends StatelessWidget {
  const FirstScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () async {
        final returnedData = await Navigator.of(context).push<String>(
          MaterialPageRoute(builder: (context) => const SecondScreen()),
        );
        
        if (returnedData != null) {
          // SecondScreen에서 돌아온 후 데이터 갱신 로직 수행
          debugPrint('Received payload: $returnedData');
        }
      },
      child: const Text('Navigate to Second Screen'),
    );
  }
}

class SecondScreen extends StatelessWidget {
  const SecondScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {
        Navigator.of(context).pop('Payload from Second Screen');
      },
      child: const Text('Return to First Screen'),
    );
  }
}

태그: Flutter AppLifecycleState WidgetsBindingObserver Dart Navigator

9월 11일 16:40에 게시됨