앱의 중요 기능 중 하나인 알람은 서버에서 fcm메시지를 수신 받으면 사용자가 미션을 완수할 때 까지 알람이 꺼지지 않고 반복해야한다.
백그라운드 처리
노티피케이션을 통한 화면 이동
이 두가지를 적절히 조합하면 쉽게 될 줄 알았다.
처음 생각한 방법은 NotificationController라는 싱글톤 객체를 만들어 앱 전체에서 상태를 공유할 수 있게 하는 것이다.
Future<void> onBackgroundHandler(RemoteMessage message) async {
NotificationController nc = NotificationController();
Timer.periodic(const Duration(seconds: 5), (timer) async{
nc.showNotification();
if(nc.isSelected) timer.cancel();
});
return Future.value();
}
설명하자면 fcm메시지를 수신받으면 onBackgroundHandler가 호출되는데 그 안에서 Timer를 통해 원하는 로직을 반복하고 미션을 완수하면 NotificationController의 isSelected를 true로 바꿀 생각이었다.
미션 페이지가 없기 때문에 notification을 클릭 했을 시 true로 바꾸게 설정했는데 결과가 처참한게
이런 식으로 화면 이동까지 했는데 notification은 계속 나타난다. 그 말은 Timer가 계속 돌고 있고 if문안의 조건이 충족되지 않는다는 뜻이다.
이유를 아무리 생각해도 모르겠어서 이것저것 시도해 봤다.
1. Timer형의 전역 변수를 생성해 핸들러 안에서 생성되는 타이머 변수에 대입해서 앱 내에서 timer.cancel을 호출한다.
2. SharedPreference나 Hive같은 로컬 db를 이용해 값 저장 후 반복 루프 안에서 검사한다.
그러던 중 디버그 콘솔에 찍힌 로그가 눈에 띄었는데
핸들러를 실행하는 Isolate를 따로 실행한다는걸 알게 되었다.
개발자가 작성한 백그라운드 로직이 실행되는 과정을 간단하게 설명하자면
1. 앱의 상위 레벨에서 메시지를 수신했을 때 실행해야 하는 Dart코드를 등록을하면
2. MethodChannel을 통해 Native에서 핸들러 메소드를 수신받는다.
3. Native영역에서 FlutterEngine객체를 만들고 전달 받은 Dart 코드를 넘겨주고
4. 생성된 FlutterEngine객체가 로직을 실행한다.
사실 이런 패키지가 native코드로 작성된 걸 MethodChannel을 통해 flutter단에서 사용하는 걸 알고는 있었는데 백그라운드 핸들러가 Dart코드로 어떻게 가능한지 의문이긴 했었다.