🐦 Flutter

[Flutter] 푸시 알람 눌렀을 때 다른 페이지로 이동

ji-hyun 2022. 11. 17. 21:50

스타트업 다니면서 많은 프로젝트를 해왔지만 이번 기능(프로젝트라고 하기엔 애매함😅) 은 기록을 남기고 싶어서 글을 작성하게 되었다.

예시 코드가 없었기 때문에 개인적으로 어려웠고 구현은 해봤지만 이게 맞는 방법인지는 모른다.

하지만 그동안 내가 사용한 어플들에서 푸시 알람이 오고 눌렀을 때 다른 페이지로 가는 방법이 어떤 방식인지 알 수 있게 되어서 정말 신기했다..!

그래서 이 신기함(?)의 느낌을 기록하고 싶다..ㅎㅎ

 

 

 

 

처음에는 간단하다고 생각했다.

그리고 나는 예전에 한 번 호기심으로 깃허브 레포를 파서 푸시 알람 오게 만드는 것도 연습해봤기 때문에 금방 끝날 것이라고 생각했다.

그런데 생각보다 그리 간단하지 않았다.

아무 페이지 없는 상태에서 푸시 알람 오게 만들기는 쉬웠지만 기존 프로젝트에서 푸시 알람을 눌렀을 때 다른 페이지로 이동하는 방법은 여러가지 생각할 것이 많았다. (그리고 우리 프로젝트가 스파게티 코드여서 더 헤맸었던 것 같다😢)

 

 

 

 

먼저 푸시 알람은 기본적으로 앱의 상태 즉 포그라운드, 백그라운드, 종료 상태에 모두 다르게 핸들링하게 된다.

이것은 flutterfire 에 더 자세히 써져있고 친절하니 공식 문서를 참고하는 것이 좋다.

정리해보면

 

 

 

 

< Foreground >

To listen to messages whilst your application is in the foreground, listen to the onMessage stream.
 
final localNotification = Get.find<LocalNotificationService>();

FirebaseMessaging.onMessage.listen((RemoteMessage rm) async {
      dev.log("FCM.onMessage.listen()");
      dev.log(rm.notification?.title ?? '');
      dev.log(rm.notification?.body ?? '');
      
      RemoteNotification? notification = rm.notification;
      AndroidNotification? android = rm.notification?.android;
      
      if (notification != null && android != null) { // Android 는 따로 UI를 만들어줘야 함
      	localNotification.show(
          notification.hashCode,
          notification.title ?? '',
          notification.body ?? '',
        );
      }
    });

 

iOS 와 Android 는 모두 앱이 Foreground 상태일 때 푸시 알람이 오면 여기로 stream 을 listen 하게 된다.

그런데 iOS 는 기본 Apple 의 노피티케이션 UI 을 쓰게 되어서 세팅할 필요가 없지만, Android 는 따로 노티피케이션 UI 를 만들어줘야 한다. 그래서 Flutter 의 local notification 패키지를 써서 Android 때에만 channel 을 만들고 이를 show 해주었다.

 

 

 

 

 

이때 Android 에서 푸시 알람을 눌렀을 때 핸들링하는 코드는 역시 local notification 패키지에서 핸들링한다. 

 

 await flutterLocalNotificationsPlugin.initialize(
      const InitializationSettings(
        android: AndroidInitializationSettings('@drawable/ic_cashfi_noti'),
        iOS: DarwinInitializationSettings(),
      ),
      onDidReceiveNotificationResponse: (NotificationResponse details) async { // 여기서 핸들링!
        print('onDidReceiveNotificationResponse - payload: ${details.payload}');
        final payload = details.payload ?? '';

        final parsedJson = jsonDecode(payload);
        if (!parsedJson.containsKey('routeTo')) {
          return;
        }

        Get.toNamed(parsedJson['routeTo']);
      },
    );

 

 

Android 는 Foreground 상태에서 푸시 알람을 누르면 onDidReceiveNotificationResponse 에서 핸들링한다.

 

 

 

 

 

 

< Background >

Also handle any interaction when the app is in the background via a Stream listener
 

 

    FirebaseMessaging.onMessageOpenedApp.listen((message) {
      _handleMessage(message);
    });

 

 

위는 앱이 백그라운드 상태일 때 푸시 알람을 누르면 핸들링하는 코드이다.

그런데 iOS 는 Foreground 상태일 때 푸시 알람을 누르면 이쪽으로 오게 된다. 왜인지는 모른다.

즉, iOS 는 Foreground 상태일 때 onMessage.listen 을 통해 푸시 알람을 listen 하게 되고 이 푸시 알람을 눌렀을 때 onMessageOpendApp.listen 으로 핸들링하게 되는 것이다.

이게 많이 헷갈렸다.

 

 

 

 

 

 

< Terminated >

Get any messages which caused the application to open from a terminated state.

 

 

 RemoteMessage? initialMessage = await FirebaseMessaging.instance.getInitialMessage();
    if (initialMessage != null) {
      _handleMessage(initialMessage);
    }

 

위는 앱이 종료된 상태일 때 푸시 알람을 누르면 핸들링하는 코드이다.

핸들링에서는 우리가 이동하고 싶은 페이지를 라우팅해주면 된다.

 

 

 

 

아무튼 내가 초반에 헤맸던 것은 푸시 알람을 listen 하는 것과 탭했을 때 핸들링하는 곳이 다르다는 것, 그리고 플러터의 local notification 의 난해한 예시 코드였던 듯 싶다.

결국 난 플러터 local notification 패키지의 예시 설명대로 하지 않고 onDidReceiveNotificationResponse 에서 간단하게 라우팅하는 등의 처리를 했다. (다시 말하지만 안드로이드일 경우임)

 

 

 

 

 

 

또 하나의 난관, 로그인

그래.. 푸시 알람을 누르면 다른 페이지로 이동하게 핸들링하면 되지! 

음 아주 간단한 생각이었다.

생각을 더 해보니 로그아웃된 상태에서 페이지 이동하게 된다면?? 

나는 호기심 대마왕이기 때문에 다른 어플들을 직접 테스트해보았다. 

내가 자주 사용하는 앱 에이블리와 배민에서 로그아웃 해보고 기존에 와있던 푸시 알람을 눌러보았다. (특히 주문완료된 푸시 알람)

 

 

기억이 확실하지는 않지만

배민 -> 로그인 창이 뜬다

에이블리 -> 에러 화면이 뜨거나, 로그인해주세요 스낵바가 뜨며 메인페이지로 이동한다.

 

 

 

그래서 나도 로그인 상태 유무에 따라 페이지를 다르게 핸들링해야겠다는 생각이 들었다.

그래서 로그아웃된 상태일 때는 푸시 알람이 오긴 하지만 탭했을 때는 로그인하는 홈페이지로 라우팅하였다.

이것은 탭했을 때의 핸들링이므로 handleMessage 메서드에서 라우팅하면 된다.

 

void _handleMessage(RemoteMessage message) async {
    dev.log('메시지 확인:: ${message.data.toString()}');

    final loggedIn = authManager.isLoggedIn;
    if (loggedIn == false) {
      print('# [Auth] Not logged in, go to home');
      Get.offAllNamed('/home');
      return;
    }

    if (message.data.containsKey('페이지 이동 키값')) {
      await Get.toNamed('/이동페이지');
      return;
    }
  }

 

 

 

 

 

 

푸시 알림이 오면 알림이 바로 증가되었으면 좋겠어요..

PM 이자 팀장님의 요구사항이었다.

현재 내가 개발하고 있는 어플의 메인 페이지 화면은 다음과 같이 오른쪽 상단 종 알람 표시이다.

여기서 알림이 오면 알림 숫자가 뜬다.

 

 

 

 

사실 이것도 생각할 것이 더 있다.

무작정 알림 total 값을 가져오는 Api 를 호출하면 되겠군.. 했다가 나중에 에러 사항을 발견하였다.

푸시알람이 오면 바로 알림 total 값을 가져와야 하므로 onMessage.listen 에다가 알림 total Api 를 호출하였다.

 

 

이것이 로그인 되어 있는 상태에서는 total 값을 가져오는 것에 문제 없었다.

하지만 문제는 로그아웃된 상태이다.

로그아웃 상태에서 푸시 알림이 왔을 때 알림 total Api 를 호출해버리니 Access Token 값이 없어서 푸시 알람이 오자마자 에러가 난다.

 

-> 로그인 상태일 때에만 알림 total Api 를 호출해야 한다.

 

 

final localNotification = Get.find<LocalNotificationService>();

FirebaseMessaging.onMessage.listen((RemoteMessage rm) async {
      dev.log("FCM.onMessage.listen()");
      dev.log(rm.notification?.title ?? '');
      dev.log(rm.notification?.body ?? '');
      
      final loggedIn = authManager.isLoggedIn;
      if (loggedIn == true) {
        await noticeManager.getNotice;   // 로그인 상태에서만 알림 Api 호출!
      }
      
      if (notification != null && android != null) { 
      	localNotification.show(
          notification.hashCode,
          notification.title ?? '',
          notification.body ?? '',
        );
      }
    });

 

 

따라서 위와 같이 푸시 알람이 올 때 로그인 상태에서만 알림 total Api 를 호출하면 된다.

그리고 탭했을 때 알림 read Api 를 호출하면 될 것이다.

 

 

 

 

 

 

다른 플러터 프로젝트에서는 어떻게 하는지는 모르겠으나 내가 생각한 로직은 이러했다.

생각보다 생각해야 할 것(?)이 많아서 개인적으로는 흥미로웠다. 다른 앱도 이런 로직이지 않을까 푸시 알람만 오면 생각해보게 된다. 이게 바로 직업병..일까?

아무튼 나는 너무 재밌었다.