ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • iOS에서는 죽고 Android에서는 지나가는 Flutter 코드가 있습니다
    AI Agent/flutter 2026. 7. 9. 09:00
    728x90
    반응형

    어두운 책상에서 머리를 맞댄 안드로이드폰과 아이폰
    안드로이드는 통과, iOS 심사 빌드만 죽던 밤

    금요일 오후였습니다.
    안드로이드 QA를 전부 통과시키고, 이제 iOS 심사용 빌드만 올리면 주말이라 홀가분한 마음이었습니다.
    빌드를 올리고 커피 한 잔 내리러 갔다 왔더니, 삼십 분쯤 사이에 크래시 리포트가 여러 건 쌓여 있었습니다.
    심사 리뷰어가 앱을 켜자마자 특정 화면에서 앱이 닫혔다는 내용이었습니다.
    같은 코드, 같은 Dart 파일인데 안드로이드에서는 개발 내내 단 한 번도 안 났던 크래시였습니다.
    순간 등이 좀 서늘했습니다.
    심사에서 이렇게 터지면 그대로 반려니까요.

    재현이 안 되는 크래시를 로그로만 쫓았다

    제일 곤란했던 건, 그 크래시를 제 손에서 재현할 수가 없었다는 점입니다.
    회사 테스트용 아이폰은 마침 다른 사람이 쓰고 있었고, 제 앞엔 시뮬레이터뿐이었는데 시뮬레이터에서는 아무리 눌러도 멀쩡히 돌았습니다.
    리포트를 하나씩 열어보니 크래시가 전부 같은 화면, 그러니까 처음 진입할 때 캐시를 만드는 그 화면에 몰려 있었습니다.
    결국 남은 단서는 리포트에 딸려 온 스택 트레이스 한 뭉치뿐이었습니다.

    처음엔 권한 문제일 거라고 넘겨짚었습니다.
    iOS는 권한에 까다로우니까 저장소 접근이 막힌 거겠거니 하고 Info.plist부터 뒤졌죠.
    근데 아무리 봐도 그 화면은 특별한 권한을 쓰지도 않았습니다.
    엉뚱한 데서 한참 시간을 버리고 나서야 스택 트레이스를 처음부터 차분히 읽었습니다.
    익숙한 Dart 프레임들 사이에, 파일에 뭔가를 쓰려다 예외가 난 지점이 딱 보였습니다.
    앱이 문서 디렉터리 밑에 캐시 폴더를 만들고 거기에 파일을 쓰는 코드였는데, 저는 그 줄을 짤 때 폴더가 당연히 있을 거라고 생각했습니다.
    안드로이드에서는 실제로 늘 있었으니까요.

    시뮬레이터가 멀쩡했던 진짜 이유

    확인차 그 write 부분을 try/catch로 감싸 예외를 통째로 로그에 찍게 한 다음, 이번엔 시뮬레이터에서 일부러 그 캐시 폴더를 지운 채로 앱을 켰습니다.
    그랬더니 시뮬레이터에서도 똑같이 터졌습니다.
    그동안 시뮬레이터가 멀쩡했던 건 코드가 옳아서가 아니라, 이전 실행이 만들어둔 폴더가 그대로 남아 있었기 때문이었습니다.
    재현이 안 됐던 게 아니라, 재현 조건을 제가 안 맞춰준 것뿐이었죠.

    앱을 새로 깐 심사 기기에는 그 폴더가 있을 리 없었습니다.
    그래서 리뷰어가 앱을 처음 켜는 그 순간, 없는 경로에 바로 쓰려다 앱이 그대로 닫힌 겁니다.
    개발하는 내내 제 기기에는 늘 이전에 만든 폴더가 남아 있었으니, 저는 이 크래시를 만날 기회 자체가 없었던 거고요.

    문자열로 조립한 경로가 화근이었다

    정확히는 경로를 문자열로 직접 이어 붙인 게 문제였습니다.
    흔히 보던 경로 모양을 머릿속에 그려놓고 짠 코드가, iOS 샌드박스에서는 다른 위치를 가리켰습니다.
    그 자리에 디렉터리가 만들어지기도 전에 파일부터 쓰려 했으니 터질 수밖에 없었습니다.

    final dir = await getApplicationDocumentsDirectory();
    final cacheDir = Directory('${dir.path}/thumb_cache');
    if (!await cacheDir.exists()) {
      await cacheDir.create(recursive: true);
    }
    

    경로를 손으로 조립하지 않고 path_provider가 돌려주는 디렉터리에서 시작하는 것, 그리고 쓰기 전에 존재 여부를 먼저 확인하는 것.
    크래시를 반나절 쫓은 끝에 실제로 고친 건 이 몇 줄이 전부였습니다.
    허탈할 만큼 작은 수정이었지만, 그 작은 가정 하나가 두 OS를 갈라놓고 있었습니다.

    그 뒤로 저는 경로가 필요한 코드를 짤 때 절대 슬래시를 손으로 이어 붙이지 않습니다.
    문서 디렉터리든 임시 디렉터리든 언제나 path_provider가 주는 값에서 출발하고, 디렉터리는 쓰기 직전에 create(recursive: true)로 한 번 보장하고 들어갑니다.
    습관 하나 바꿨을 뿐인데, 그 뒤로 iOS와 안드로이드 사이에서 경로 때문에 앱이 닫히는 일은 없어졌습니다.

    안드로이드 초록불은 증거가 아니었다

    이 일에서 제일 뜨끔했던 건, 제가 안드로이드에서 잘 돈다는 걸 "테스트를 통과했다"로 착각했다는 점입니다.
    안드로이드 QA가 초록불이었던 건 그 플랫폼에서 그 경로가 우연히 있었기 때문이지, 코드가 옳아서가 아니었습니다.
    한쪽에서 통과한 게 다른 쪽의 안전을 보장하지 않는다는 걸, 심사 반려 문턱까지 가서야 몸으로 배웠습니다.
    나중에 같은 앱을 웹으로 띄웠을 때 지원 안 되는 plugin 때문에 비슷하게 한 번 더 뒤통수를 맞긴 했지만, 그건 또 다른 날의 이야기고요.

    두 줄을 고치고 다시 심사에 올린 날은, 커피를 내리러 가지 않았습니다.
    앱을 지웠다 새로 깔고 그 화면에 처음 들어가 보기를 몇 번 반복한 다음, 리포트 알림이 안 뜨는 걸 한참 동안 그냥 지켜봤습니다.

    원문 기준

    원문 기준: https://docs.flutter.dev/platform-integration

    원문 본 날짜: 2026-07-01.
    올리기 전에 원문이 그새 바뀌진 않았는지만 다시 확인합니다.

    728x90
    반응형
Designed by Tistory.