개발 slecs

Google/Apple 로그인 콜백 크래시 수정

목차

크로스 플랫폼 앱(Flutter iOS)에서 소셜 로그인 콜백을 처리할 때 일어나는 버그를 잡았다. Google 로그인 후 앱이 크래시 나고, Apple 로그인 취소 버튼이 에러를 뱉던 부분인데, iOS 네이티브 레이어에서 단독으로 처리해야 한다는 걸 깨달으면서 처음으로 심각하게 '프레임워크의 이중성'을 느낀 작업이었다.

왜 이게 문제가 됐나

Flutter로 크로스 플랫폼 앱을 만들 때 가장 까다로운 부분이 바로 네이티브 콜백이다. 특히 인증 플로우는 더한데, 이유는 간단하다:

  • Google 로그인: OAuth 콜백을 받으려면 iOS 앱 스키마(app scheme)를 Google Console에 등록해야 하고, 그 스키마로 들어오는 딥링크를 AppDelegate에서 먼저 받아야 함
  • Apple 로그인: ASAuthorizationController의 delegate 메서드들이 UIViewController의 라이프사이클과 밀접하게 연결되어 있음

Dart 단에서 sign_in_with_google, sign_in_with_apple 라이브러리들을 쓰면 편하지만, 그 라이브러리들은 결국 iOS의 네이티브 이벤트를 받아야 하는데, 그 진입점이 AppDelegate이라는 건 많은 팀이 간과한다. Flutter는 Dart 영역으로 대부분 처리되지만, 어느 순간 네이티브 콜백이 들어오면 AppDelegate가 가장 먼저 잡아채기 때문이다.

두 가지 문제

문제 증상 원인
Google 콜백 크래시 로그인 성공 후 리다이렉트 URL 처리 중 앱 강제 종료 AppDelegate의 application:openURL:options: 미구현 또는 부분 처리
Apple 취소 에러 "취소" 버튼 클릭 후 에러 메시지 표시, UI 미반응 Apple 인증 에러 콜백이 Dart 레이어로 제대로 전달되지 않음

두 플랫폼이 다르게 동작하는 이유는 인증 흐름의 '시점'이 다르기 때문이다:

  • Google: 외부 브라우저(Safari)로 나갔다가 앱 스키마로 콜백 → 반드시 AppDelegate를 거쳐야 함
  • Apple: 앱 내부 UIViewController(ASAuthorizationController)가 처리하지만, 취소나 에러 상태를 Dart의 Future로 받아야 함

해결책: AppDelegate 단독 처리

변경한 파일 구성:

// AppDelegate.swift
// Google 리다이렉트 URL 처리를 일원화
func application(
    _ app: UIApplication,
    open url: URL,
    options: [UIApplication.OpenURLOptionsKey : Any] = [:]
) -> Bool {
    // Google 로그인 콜백 처리
    if GIDSignIn.sharedInstance.handle(url) {
        return true
    }
    return false
}

왜 AppDelegate에서 모두 처리해야 하는가:

  1. 네이티브 이벤트의 진입점: iOS 시스템이 먼저 AppDelegate의 application:openURL:options: 을 호출함. 이 메서드가 true를 반환해야 앱이 URL 처리를 "완료했다"고 인식
  2. Dart 플러그인의 한계: Google Sign-In의 Flutter 플러그인도 내부적으로 AppDelegate를 후킹하지만, 만약 우리가 이미 AppDelegate를 수정 중이라면 explicit하게 처리하는 게 더 안전
  3. Apple의 특수성: Apple Sign-In은 비동기 콜백을 주지 않으므로, 취소 상태를 method channel(Platform Channel)로 Dart에 전달해야 함

파일 변경의 의미

파일 역할 이번 변경
AppDelegate.swift iOS 앱 생명 주기 & 네이티브 진입점 Google/Apple 콜백 처리 메서드 추가
login_screen.dart Dart 단의 로그인 UI 에러 핸들링 보강, method channel 리스너 추가 가능
pubspec.yaml 패키지 의존성 플러그인 버전 핀 또는 새 플랫폼 채널 추가

구체적으로:
- AppDelegate.swift: Google OpenURL 핸들러 + Apple 에러 콜백 처리 로직
- login_screen.dart: "로그인 취소됨" / "로그인 실패" 상태를 AppDelegate의 메시지로부터 받도록 리스너 추가
- pubspec.yaml: 기존 플러그인 버전 확인 또는 새로운 플랫폼 채널 의존성 추가

회고: 크로스 플랫폼의 '숨겨진 비용'

이 버그는 기술적으로는 간단하지만, 개념적으로는 중요한 교훈을 줬다:

Flutter 같은 크로스 플랫폼 프레임워크는 90%를 추상화하지만, 나머지 10%는 각 플랫폼의 깊숙한 부분에 있다. 특히 인증, 결제, 푸시 알림 같은 '시스템 이벤트'는 플랫폼별 콜백이 필수이고, 이걸 무시하면 언젠간 터진다.

내가 배운 패턴:
1. 소셜 로그인 / OAuth 콜백 → 반드시 네이티브 AppDelegate/Activity 에서 먼저 처리 후 Dart로 전달
2. Platform Channel의 중요성 → 비동기 상태(취소, 에러)는 method channel로 통신하는 게 가장 안정적
3. 테스트 환경 분리 → Google/Apple 인증은 로컬에서 디버깅이 어려우니, staging 빌드로 자주 검증
4. 버전 호환성 → 플러그인 버전 업그레이드 후 iOS 빌드까지는 한 번의 풀 QA 필요

팀에 남은 숙제:
- Apple 콜백이 네이티브에서 정상 처리되는지 자동 테스트 추가
- Google 리다이렉트 URL이 앱 스키마 설정과 일치하는지 검증하는 docs 작성

다음엔 인증 이슈가 나면, 항상 "AppDelegate에서 어디까지 처리되고 있는가"를 먼저 질문하는 습관을 들이려고 한다.


🛒 이 글과 어울리는 추천 상품

*위 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.

댓글 0

첫 댓글 달아줘.