
사실 앱 심사 넣기 전에 너무 늘어지는 것 같아서 미뤄두었건 기능들을 넣으며 v1.2.0 버전으로 올리고 싶었지만, 기능 추가를 하기 전에 처리하고 가는 게 좋을 것 같은 업데이트 패치들이 있어 먼저 털고 가기로 했다. 이번엔 큰 건 아니고 작은 이슈들. v1.2.0 에 새로운 기능과 함께 버그픽스를 해도 무방할 정도의 사소한 것들. 하지만 이왕이면 빠르게 반영하는 게 좋을 것들.
나와 내 형제를 위한 식사를 준비하던 중 갑자기 Firebase Crashlytics로부터 메일이 왔다. 어라? 이거 오면 안 되는데? 이거 치명적 오류가 있을 때만 오는 거 아니야? 그러고 보니 오전에 안드로이드 프로덕션 출시가 완료되었다는 메일을 받았는데? 정식 출시 첫 날부터 뭔가 문제가 생긴 거야?????? 그런 생각을 하며 이메일을 확인해 보니 총 6번의 치명적 오류가 보고되었다는 내용이 보인다.
뭔가 장황하게 써 있는 걸 내가 판단하는 것보다는 AI에게 원인 분석을 맡기는 게 나을 듯하여 요청해 보니 AuthViewModel 에서 NetworkException 이 발생한 데에서 비롯된 로그였다. 네트워크가 연결되지 않은 상태로 앱을 실행하면 사용자 정보를 불러올 수 없어서 로그인이 풀리고 마는, 엄밀히 말하면 예외처리가 제대로 되지 않았던 부분이다. 인증을 구현한 시점이면 Riverpod도 MVVM도 이해도가 떨어지던 초반 작업이었는데 이후에 다시 검토하지 못했던 게 터진 모양이다.
치명적 오류라고 할 정도는 아니었고, 네트워크 연결이 안 되어 있을 경우에는 로컬 캐시 인증으로 대체하도록 코드를 수정하였다. 그리고 사소한 네트워크 오류도 치명적 오류로 잡히고 있는 로그 기록도 오류의 심각성을 조정하기로 했다. 제어 가능한 비즈니스 예외와 단순 통신 장애는 치명적 오류로 집계할 필요 없으니 말이다.
네이티브 안드로이드 개발자를 대상으로 하는 경고라 Flutter로 구현하는 입장에서는 무시해도 되는 경고지만 계속 경고가 보이면 신경쓰이고 거슬리니까 처리할 수 있으면 처리하기로 했다. 확인해 보니 Android 15(API 레벨 35)부터 구글이 앱 화면이 상단 상태 표시줄과 하단 제스처 내비게이션 바 뒤쪽까지 완전히 꽉 차게 렌더링되는 Edge-to-Edge 모드를 기본값으로 강제하면서, 이를 처리하는 코드가 포함되지 않은 앱에 경고를 보내는 거라고 하더라.
Flutter 프레임워크는 네이티브 뷰를 거치지 않고 자체 그래픽 파이프라인으로 렌더링하기 때문에 여백이 자연스럽게 확보되므로 크게 신경쓰지 않아도 되는 부분이긴 하다. 이걸 처리하는 게 오버엔지니어링일 수도 있을 것 같기도 하고. 생각해 보면 로컬에서 앱 빌드할 때도 우리한테 하는 말이 아닌, 우리가 사용한 package를 대상으로 한 경고들을 일상적으로 마주하기도 한다.
네이티브 앱 개발 시 사용해야 할 패키지를 누더기처럼 붙이는 것보다는 시스템 바가 투명해지는 것과 관련하여 아이콘 색상을 조정하는 등 theme.dart 수준에서의 처리만 해주기로 했다. 플레이스토어 경고는 계속 사라지지 않고 남아있겠지만 일단은 이 정도가 최선인 것 같다.
네트워크가 끊긴 상태에서 로컬 캐시 인증 데이터로 무사히 홈 탭에 도달하면, 날씨를 불러오고 있다는 문장이 반겨준다. 그런데 타임아웃되어 날씨 정보를 가져오지 못했을 때도 날씨를 불러오는 중이라고 뜨며 마치 무한 로딩이 되고 있는 것처럼 보인다. 실제로는 그 어떤 재시도도 하지 않고 머물러 있음에도 불구하고 말이다. 무한 로딩으로 보이는 것보다는 날씨를 불러올 수 없다고 명확히 해주는 게 나을 것 같다.
그런데 홈 화면에는 새로고침이 없어 한 번 '날씨를 불러올 수 없습니다'가 뜨면 네트워크가 복구되어도 그 상태 그대로다. 새로고침을 어떻게 처리하면 좋을까 하다가, 홈 화면에서는 상단 스크롤로 새로고침을 하기보다는 데이터 로딩 실패 시에 한해 날씨 영역을 터치하면 새로고침이 되도록 구현하였다. 이렇게 하는 게 괜찮은 방식인지 AI에게 검토를 요청했는데, [사용자는 실패 문구를 보면 본능적으로 탭을 해봅니다.] 라며 [행동 심리학 기반 탭 인터랙션]이라 UX적으로 괜찮은 방식이라고 응답 받았다.
하는 김에 홈 화면의 마지막 세션 스케치에도 탭 이벤트 처리를 넣었다. 데이터 로딩 실패 시 날씨를 탭하는 것과 마찬가지로 홈 화면을 새로고침한다. 그리고 정상적으로 로딩이 되었다면 피드 공유 게시물인지 여부에 따라 피드 게시물 화면 또는 기록 상세 화면으로 이동한다.
난... 기본적으로 사람에게 관심이 많다. 특히 나와 교집합이 있는 사람에게 관심이 많다. 사람과 직접 상호작용하는 건 아직 많이 어설프지만 사람 자체에는 흥미가 있어 종종 구경하곤 한다. 그래서 괜히 Velog에서도 SeSAC 키워드를 눌러 다른 수강생들의 글을 구경하기도 하는데, 몇 개월 전에는 다른 캠퍼스 수강생도 여럿 있고 같은 수업 듣는 분도 있고 하는 반면 최근엔 잘 안 보여서 (아무래도 연말에는 수업을 잘 안 하니까?) 좀 아쉽기도 하고, 새로 발견한(?) 분도 있어서 반갑기도 하고 ㅋㅋ 라는 건 여담이고.
가끔 강사님의 이전 기수 수강생들 블로그도 살펴본다. 대체로 이제는 블로그 활동을 안 하고 있더라. 2년 전 수강생 블로그의 첫 화면에 아직 2년 전 공부 기록이 보이기도 하고. 하여간 그 중 하나에서 가로 모드를 막고 세로 모드를 강제하는 코드를 보고, 어라? 난 이거 안 해놨는데? 지금 화면을 돌리면 어떻게 보이지???
일단 웹 빌드를 준비하며 화면 가로 크기에 따라 NavigationBar 또는 NavigationRail 로 조정하는 게 구현되어 있었기에 가로 모드로 돌리면 NavigationRail 로 왼쪽에 네비게이션이 뜬다. 그것 말고는 대체로 레이아웃 조정이 필요하다. 피드 탭은 그나마 봐줄 만하지만 역시 AppBar가 차지하는 높이가 너무 크다. 기록 탭은 캘린더도 다 보이지 않는다. 가로모드를 살릴 거면 가로모드에서는 캘린더와 목록을 상하 배치가 아닌 좌우 배치로 해야 할 것 같다. 나 탭도 갤러리 부분의 높이가 너무 좁다. 홈 탭도 배치 조정이 필요할 것 같고.
여러 가지 생각해 보았을 때, 가로 모드를 보장하는 경우의 오버헤드가 너무 크다. 운동 앱을 가로로 쓸 일은 거의 없고 일반적인 운동 앱도 세로 모드만 지원하는 경우가 많다. 자전거 탈 때 정도는 핸들 부근에 가로로 꽂아놓고 달리는 것도 나쁘지 않지만 그거 하나를 위해서 가로 모드를 구현하는 건 굳이? 가로 모드는 과감히 배제하고 가는 게 합리적일 것 같다.
안드로이드의 경우 예전엔 AndroidManifest.xml 파일에 android:screenOrientation="portrait" 를 지정하는 방식을 사용했지만, Android 16부터는 이 옵션이 무시될 예정이라고 한다. 따라서 Flutter 코드의 main.dart 에 세로 모드 고정 코드를 작성해야 한다. 그런데 애플의 경우 아이폰과 아이패드에 각각 적절한 제약을 걸도록 Info.plist 에서 이를 설정하는 게 표준이라고 한다. 아이폰은 세로 고정으로 하되 아이패드는 가로모드로 돌릴 수 있도록 하는 식이다. 따라서 Flutter 코드에는 안드로이드일 경우에 한해 세로 고정을 하고, 아이폰은 Info.plist 에서 아이패드가 아닌 아이폰만 세로 고정을 하도록 작성하기로 했다.