[Flutter 기초] AppLifeCycle과 WidgetBindingObserver에 대해 알아보자

Yellowtoast·2023년 2월 2일
1

Flutter 기초

목록 보기
4/4

App의 LifeCycle이란?

특정 앱을 실행하였을 때, 이 앱이 잠시 백그라운드에 넘어갈 수도 있고, 혹은 상태바를 내리는 액션을 통해 앱이 잠시 중지되었을 수도 있다. 이와 같이 앱 전체 프로세스 상태를 이 글에서 App LifeCycle이라고 칭하기로 한다.

그렇다면, Flutter에서는 앱의 LifeCycle을 몇가지 정도로 구분하고 있을까?
플러터 내부에서는 이 LifeCycle의 상태를 'AppLifecycleState' 이라는 enum 값으로 관리하고 있다.

AppLifecycleState은 총 4개의 상태값으로 이루어져 있다.

Resumed, Inactive, Paused, Detached

///  * [WidgetsBindingObserver], for a mechanism to observe the lifecycle state
///    from the widgets layer.
enum AppLifecycleState {
  /// The application is visible and responding to user input.
  resumed,

  /// The application is in an inactive state and is not receiving user input.
  ///
  /// On iOS, this state corresponds to an app or the Flutter host view running
  /// in the foreground inactive state. Apps transition to this state when in
  /// a phone call, responding to a TouchID request, when entering the app
  /// switcher or the control center, or when the UIViewController hosting the
  /// Flutter app is transitioning.
  ///
  /// On Android, this corresponds to an app or the Flutter host view running
  /// in the foreground inactive state.  Apps transition to this state when
  /// another activity is focused, such as a split-screen app, a phone call,
  /// a picture-in-picture app, a system dialog, or another window.
  ///
  /// Apps in this state should assume that they may be [paused] at any time.
  inactive,

  /// The application is not currently visible to the user, not responding to
  /// user input, and running in the background.
  ///
  /// When the application is in this state, the engine will not call the
  /// [PlatformDispatcher.onBeginFrame] and [PlatformDispatcher.onDrawFrame]
  /// callbacks.
  paused,

  /// The application is still hosted on a flutter engine but is detached from
  /// any host views.
  ///
  /// When the application is in this state, the engine is running without
  /// a view. It can either be in the progress of attaching a view when engine
  /// was first initializes, or after the view being destroyed due to a Navigator
  /// pop.
  detached,
}

App의 LifeCycle에 따라 로직을 수행하기 위해서는 어떻게 해야 할까?

편리하게도, 플러터는 이 기능을 위해 WidgetsBindingObserver라는 abstract class를 구현하여 사용하도록 한다.

  1. 우리는 이 앱의 라이프사이클을 관리하고자 하는 클래스에 WidgetsBindingObserver를 mixin한다.(with 키워드를 사용하여) 하지만 이 클래스는 Abstract 클래스이기 때문에 내부의 함수를 오버라이딩 한다 하더라도 아무런 영향을 미치지 못한다.
class HomeView extends StatefulWidget {
  const HomeView({Key? key}) : super(key: key);

  
  State<HomeView> createState() => _HomeViewState();
}

class _HomeViewState extends State<HomeView> with WidgetsBindingObserver {
  ...
 }
  1. 그렇다면 이 Abstract Class인 WidgetsBindingObserver가 제 역할을 다하도록 어떻게 할 수 있을까? 그것은 바로 WidgetsBinding클래스의 인스턴스를 만들어, 구현한 observer를 붙이는 것이다.
class _HomeViewState extends State<HomeView> with WidgetsBindingObserver {

  
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }
  1. 또한 이 observer는 반드시 dispose해주어야 한다.
  
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

WidgetsBindingObserver는 어떤 함수를 제공하는가?

플러터 공식문서에 나오는 내용을 보면 아래와 같은 함수들을 제공한다.


다양한 함수에 대한 자세한 내용은 다른 게시글에서 다루도록 하고, 우선 가장 많이 사용되는 함수인 didChangeAppLifecycleState 에 대해 알아보도록 하자.

didChangeAppLifecycleState(AppLifecycleState state)void

해당 함수는 앱의 현재 상태에 따라 앞전에 언급한 AppLifecycleState를 리턴한다.

didChangeAppLifecycleState 구현하기

 
  void didChangeAppLifecycleState(AppLifecycleState state) {
    //https://api.flutter.dev/flutter/dart-ui/AppLifecycleState-class.html
    switch (state) {
      case AppLifecycleState.resumed:
        // 앱이 표시되고 사용자 입력에 응답합니다.
        // 주의! 최초 앱 실행때는 해당 이벤트가 발생하지 않습니다.
        print("resumed");
        break;
      case AppLifecycleState.inactive:
        // 앱이 비활성화 상태이고 사용자의 입력을 받지 않습니다.
        // ios에서는 포 그라운드 비활성 상태에서 실행되는 앱 또는 Flutter 호스트 뷰에 해당합니다.
        // 안드로이드에서는 화면 분할 앱, 전화 통화, PIP 앱, 시스템 대화 상자 또는 다른 창과 같은 다른 활동이 집중되면 앱이이 상태로 전환됩니다.
        // inactive가 발생되고 얼마후 pasued가 발생합니다.
        print("inactive");
        break;
      case AppLifecycleState.paused:
        // 앱이 현재 사용자에게 보이지 않고, 사용자의 입력을 받지 않으며, 백그라운드에서 동작 중입니다.
        // 안드로이드의 onPause()와 동일합니다.
        // 응용 프로그램이 이 상태에 있으면 엔진은 Window.onBeginFrame 및 Window.onDrawFrame 콜백을 호출하지 않습니다.
        print("paused");
        break;
      case AppLifecycleState.detached:
        // 응용 프로그램은 여전히 flutter 엔진에서 호스팅되지만 "호스트 View"에서 분리됩니다.
        // 앱이 이 상태에 있으면 엔진이 "View"없이 실행됩니다.
        // 엔진이 처음 초기화 될 때 "View" 연결 진행 중이거나 네비게이터 팝으로 인해 "View"가 파괴 된 후 일 수 있습니다.
        print("detached");
        break;
    }
  }

의문점1 : 1초마다 프린트문을 실행하는 Timer는 앱의 상태마다 어떻게 실행될까?

1초가 지날때마다 초를 프린트 하는 Timer를 구현하여, initState에서 timer를 시작하는 코드를 구현하였다.

class _HomeViewState extends State<HomeView> with WidgetsBindingObserver {
  late final Timer _timer;

  
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);

    _startTimer();

  }

  void _startTimer() {
    _timer = Timer.periodic(const Duration(seconds: 1), (timer) {
      print(timer.tick);
    });
  }

앱이 Resumed, Inactive, Paused 상태일때에는 timer가 계속 프린트되지만, Detached(앱을 나간 후 백그라운드에서도 없앴을 경우) 에야 비로소 timer는 중지된다.

의문점 2 : background에 있는 앱은 언제 detached 상태로 전환될까?

이것은, iOS와 Android 에 따라 다른데, 오디오 프로그램이나 네비게이션 같은 어플리케이션은 자체적으로 background에서 오래 유지되지만, 다른 어플들은 휴대폰 자체의 퍼포먼스나, 배터리 상태에 따라 다르게 유지된다.

구체적으로 앱을 일정 기간동안 background에 살아있도록 유지하고 싶다면 어떤 native의 기능을 건드려야 할지는 좀 더 공부가 필요해 보인다.

profile
Flutter App Developer

0개의 댓글