firebase_messaging 백그라운드 작업 통제 ios part

flunge·2022년 1월 4일
0

시계는 와치

목록 보기
5/5

android에서 백그라운드 작업을 수정했으니 ios차례이다.

ios fcm세팅은 구글링해서 그대로 하면 어렵지 않게 되는데 따로 fcm서버가 있을 시 페이로드에 content_available옵션을 true값을 주지 않으면 아예 클라이언트에서 수신을 못하니 참고하면 좋을거같다.
android같은 경우엔 페이로드에 임시로 "message":"test" 이렇게만 보내도 받아지는데 ios는 그렇지 않아서 클라이언트 세팅 문제로 착각해 시간을 꽤 많이 허비했다.


ios도 android랑 같은 현상이다.

알아보기


수정해야하는 코드가 위치한 경로이다. ios는 파일이 두 개있는데 하나는 헤더파일이니 실질적으론 FLTFirebaseMessagingPlugin.m하나만 건들면된다.

android 코드가 자바 코드여서 수정할 때 혹시? 했었는데 역시나 ios가 objc로 작성되어 있었고 눈앞이 깜깜해졌다. 대충 훑어봤는데 약간 visual basic같기도하다. swift는 kotlin이랑 비슷하게 생겨서 그나마 볼만한데 이건 익숙치 않으니 시작부터 하기가 싫어진다.

코드 추가

기본적인 전략은 android part와 같다.
native영역에서 background와 foreground간의 소통. 하지만 다른게 있다면 android에선 별도의 Dart엔진을 실행하는데 ios에선 애플리케이션의 백그라운드에서 실행하는 그런게 있는거같다.

이 부분인데 정확히는 모르지만 그렇게 추정하고 있다.

native

본론으로 돌아와서 추가할 코드는
1. 현재 상태를 알려줄 bool 변수
2. dart에서 invokeMethod를 날렸을 때 native영역에서의 handler

bool 변수

@implementation위에 제일 위 쪽에 선언해줬다. objc에서 bool 타입이 몇 개 더 있었는데 BOOL로 설정했다.

- (instancetype)initWithFlutterMethodChannel:(FlutterMethodChannel *)channel
                   andFlutterPluginRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
  self = [super init];
  
  ...
  
  isBackgroundRunning = NO;
  return self;
}

이름과 self = [super init]; 라인을 보니 생성자 역할을 하는 메소드 같아서 이 안에서 NO로 초기화 해준다. true, false가 아닌 YES, NO이다.
이 곳에서 초기화를 따로 하는 이유는 선언과 동시에 하고 다른 곳에서 호출하니 NULL이라고 나와서이다.

dart에서 invokeMethod를 날렸을 때 native영역에서의 handler

- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)flutterResult {
  FLTFirebaseMethodCallErrorBlock errorBlock = ^(
      
      ...
      
   FLTFirebaseMethodCallResult *methodCallResult =
      [FLTFirebaseMethodCallResult createWithSuccess:flutterResult andErrorBlock:errorBlock];
      ...
      
  } else if ([@"Messaging#startBackgroundIsolate" isEqualToString:call.method]) {
    methodCallResult.success(nil);
  } else if ([@"Messaging#askIsSelected" isEqualToString:call.method]) {
    [_channel invokeMethod:@"Messaging#answerIsSelected" arguments:(isBackgroundRunning ? @"YES" : @"NO")];
    methodCallResult.success(nil);
  } else if ([@"Messaging#setIsSelectedTrue" isEqualToString:call.method]) {
    isBackgroundRunning = YES;
    methodCallResult.success(nil);
  } else if ([@"Messaging#setIsSelectedFalse" isEqualToString:call.method]) {
    isBackgroundRunning = NO;
    methodCallResult.success(nil);
  }
      ...

handleMethodCall안에서 조금 내리면 if else가 나오고 그 아래에 추가해준다.
Messaging#startBackgroundIsolate 부분을 보면 알겠지만 하는 일이 따로 없다. ios에선 사용하지 않는다는 의미이다.

그 아래의 Messaging#askIsSelected는 background에서 호출하게 된다. 그 후 invokeMethod:@"Messaging#answerIsSelected"를 호출하고 isBackgroundRunning값에 따라 YES나 NO를 보낸다.

이러면 native영역에서의 준비는 끝났다.

dart

Future<void> onBackgroundHandler(RemoteMessage message) async {
  const MethodChannel methodChannel = MethodChannel('plugins.flutter.io/firebase_messaging');
  methodChannel.invokeMethod('Messaging#setIsSelectedFalse');

  bool isSelected = false;
  
  methodChannel.setMethodCallHandler((call){

    switch (call.method){
      case 'Messaging#answerIsSelected':
        print('Messaging#answerIsSelected : ${call.method} // ${call.arguments}');
        if(Platform.isIOS){
          if(call.arguments == 'NO') {
            isSelected = false;
          } else {
            isSelected = true;
          }
        } else{
          isSelected = call.arguments as bool;
        }
        break;
      default:
        print('default : ${call.method} // ${call.arguments}');
        break;
    }

    return Future.value();
  });

  
  Timer.periodic(const Duration(seconds: 5), (timer) {
    methodChannel.invokeMethod('Messaging#askIsSelected');

    if(isSelected) timer.cancel();
    NotificationController().showNotification();
  });

  return Future.value();
}

switch문에서의 Messaging#answerIsSelected 케이스에서 ios인지 android인지를 구분하는데 그 이유는 ios에서는 YES or NO를 반환하기 때문에 한번더 바꿔주는 과정이 필요해서이다.

결과


이번에도 2번 더 반복되고 완전히 멈춘다. 이건 나중에 알아봐야 할거같다.

마치며

objc를 맛본? 맛봤다기보다 멀리서 냄새 맡아봤다고 하는게 더 나을거같고 swift가 굳이 왜 나왔는지 알거같다.

0개의 댓글