Lifecycle(앱 상태) 이벤트 3편

Lifecycle(앱 상태) 이벤트 1편
Lifecycle(앱 상태) 이벤트 2편
Lifecycle(앱 상태) 이벤트 4편
Lifecycle(앱 상태) 이벤트 5편

get | Flutter Package
shared_preferences | Flutter Package

App Life Cycle에 대하여 정리해 놓은 인트로 참고하세요 !
App Lifecycle Intro

이전 글에 이어서 이번 글에서도 Flutter에서 앱 상태를 수신하는 또 하나의 방법인 GetX에서 수신하는 방법에 대해서 알아보도록 하자.

Flutter

이전 글에서 사용한 방식과 동일하게 하였다.

앱 상태를 수신 받아 각 앱 상태의 변경에 대해서 UI로 나타내 주고자 로컬 저장소를 사용하여 만들어 보았다.

상태가 변경될 때 상태와 시간을 shared_preferences 라이브러리를 통해 로컬 저장소에 저장한 뒤 화면에 노출 시켜 앱 상태 변화를 확인해 보자.

dependencies

dependencies:
	get: ^4.6.5
	shared_preferences: ^2.0.17

UI

상태 변경에 대해서 저장된 로컬 저장소에서 상태를 가져와 화면에 보여주는 부분의 코드이다.

GetBuilder<LifeCycleGetx>(
        init: LifeCycleGetx()..started(),
        builder: (state) {
          return Scaffold(
              appBar: appBar(title: "Life Cycle With Get X"),
              body:
                  lifeCycleUIListView(data: state.lifeCycle, context: context));
        });
ListView lifeCycleUIListView({
  required List<String> data,
  required BuildContext context,
}) {
  return ListView(
    children: [
      const SizedBox(height: 12),
      ...data.map(
        (e) => Padding(
          padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              SizedBox(
                  width: MediaQuery.of(context).size.width * 0.2,
                  child: Text(
                    e.split("/")[0],
                    style: TextStyle(
                      fontSize: 16,
                      fontWeight: FontWeight.bold,
                      color: e.split("/")[0] == "inactive"
                          ? Colors.amber
                          : e.split("/")[0] == "stop"
                              ? Colors.amber.shade200
                              : e.split("/")[0] == "detached"
                                  ? Colors.deepOrange
                                  : e.split("/")[0] == "restart"
                                      ? Colors.blue.shade200
                                      : e.split("/")[0] == "resumed"
                                          ? Colors.blue
                                          : Colors.green,
                    ),
                  )),
              const SizedBox(width: 12),
              Text(
                e.split("/")[1],
                style: const TextStyle(
                  color: Color.fromRGBO(195, 195, 195, 1),
                ),
              ),
            ],
          ),
        ),
      )
    ],
  );
}

GetxController

GetxController 생성시 StateFul과 동일하게 WidgetsBindingObserver 객체를 상속받자.

class LifeCycleGetx extends GetxController with WidgetsBindingObserver {
  List<String> lifeCycle = [];
	...
}

StateFul의 initState와 동일한 기능을 가지고 있다. WidgetsBindingObserver를 생성하자.

  
  void onInit() {
    super.onInit();
    WidgetsBinding.instance.addObserver(this);
  }

GetxController가 dispose될 때 WidgetsBindingObserver를 지워 주는 코드이다.

setLocalStorage 함수는 아래에서 살펴도록 하겠다.

 
  void onClose() {
    super.onClose();
    _setLocalStorage("Detached");
    WidgetsBinding.instance.removeObserver(this);
  }

GetxController에서도 앱의 상태를 확인할 수 있는 기능이 StateFul과 동일하게 제공된다.
didChangeAppLifecycleState에서 앱 상태를 체크하여 상태 값과 시간을 저장해두는 setLocalStorage 함수를 호출하고 앱 진입시 상태인 resumed에서는 저장해 둔 로컬 저장소 데이터를 가져와 화면에 보여주기 위한 기능인 getLocalStorage 함수를 호출하자.

 
  void didChangeAppLifecycleState(AppLifecycleState state) async {
    switch (state) {
      case AppLifecycleState.detached:
        _setLocalStorage("Detached");
        break;
      case AppLifecycleState.paused:
        _setLocalStorage("Paused");
        break;
      case AppLifecycleState.inactive:
        _setLocalStorage("Inactive");
        break;
      case AppLifecycleState.resumed:
        _setLocalStorage("Resumed");
        _getLocalStorage();
        break;
      default:
    }
  }
  Future<void> _getLocalStorage() async {
    SharedPreferences _pref = await SharedPreferences.getInstance();
    List<String> _list = _pref.getStringList(_lifeCycleKey) ?? [];
    lifeCycle = _list;
    update();
  }
  Future<void> _setLocalStorage(String value) async {
    String _saveData = "$value/${DateTime.now().toString().substring(0, 19)}";
    SharedPreferences _pref = await SharedPreferences.getInstance();
    List<String> _list = _pref.getStringList(_lifeCycleKey) ?? [];
    _list.add(_saveData);
    _pref.setStringList(_lifeCycleKey, _list);
  }

이제 결과를 확인하기 위해 앱을 백그라운드에 두었다가 앱을 진입해 보자. 이전 글에서 사용한 StateFul과 동일한 결과가 나온다. 그럼 GetxController에서는 detached 상태가 호출이 될까 ?
결과는 역시 동일하게 수신되지 않고 있다. StateFul에서 확인해본 것과 동일하게 뷰를 파괴시켜 detached 상태에서 로그를 출력해보자.

로그는 호출되지만 로컬 저장소에 저장은 되지 않고 있다.

exit(0); // IOS
SystemNavigator.pop(); // Android

Result

Android

IOS

Git

https://github.com/boglbbogl/flutter_velog_sample/blob/main/lib/life_cycle/life_cycle_getx.dart

마무리

StateFul, GetX 두 개의 방법을 사용하여 Flutter에서 앱 상태를 수신하는 방법에 대해서 살펴봤다. 상태 수신에는 문제가 없지만 앱 종료 상태인 detached 상태가 호출되지 않는다는 것을 확인하였다.

이 문제를 해결하기 위해 많은 방법을 고민하고 시도해 봤지만 Flutter에서는 도저히 해결이 되지않는다.

앱 사용자가 버튼을 눌러 앱을 종료 시키지도 않을 뿐더러, 디바이스를 강제 종료 시키거나 배터리가 없어서 꺼지게 되더라도 앱 종료를 수신받을 수 없다.

앱 종료 상태에서 API 호출을 하는 기능이 필요 하였기에, API 호출도 해봤지만 정상적인 작동이 되지 않았다.

다음 글에서는 네이티브에서 앱 상태는 Flutter와 어떻게 다르게 작동되고, Flutter에서 수신하고 수행하지 못하는 앱 종료 상태의 API 호출을 네이티브에서는 가능할지에 대해서 살펴보려고 한다.

profile
Flutter Developer

0개의 댓글