전체 글 7

Flutter에서 화면 복귀 감지하기: RouteAware와 앱 생명주기 로직 분리

들어가며채팅방에서 뒤로 가 채팅 목록으로 돌아왔을 때, 그리고 앱이 백그라운드에서 포그라운드로 복귀했을 때 채팅 목록을 갱신해야 했습니다. 처음에는 채팅 목록 화면의 initState()에서 데이터를 조회하면 충분하다고 생각할 수 있습니다. 하지만 메인 화면이 IndexedStack으로 구성돼 있었기 때문에 initState()만으로는 화면 복귀를 감지할 수 없었습니다. 이번 글에서는 다음 두 상황을 각각 감지한 방법과, 관련 코드가 MainScreen에 쌓이지 않도록 생명주기 감지를 별도 위젯으로 분리한 과정을 정리합니다.위에 쌓였던 화면이 닫혀 현재 화면으로 돌아온 경우앱이 백그라운드에서 포그라운드로 돌아온 경우IndexedStack에서는 initState가 다시 호출되지 않는다메인 화면은 홈·메시지..

Flutter 앱에 REST + Socket.IO로 실시간 채팅 붙이기

들어가며채팅 기능을 처음 구현하면서 가장 헷갈렸던 부분은 다음 질문이었습니다.“실시간 채팅이라면 소켓만 연결하면 되는 것 아닌가?”실제로 채팅방 화면을 구현하려면 다음 작업이 모두 필요합니다.사용자가 채팅방에 접근할 수 있는지 확인이전에 주고받은 메시지 조회새 메시지 실시간 수신메시지 전송연결이 끊어졌을 때 인증 토큰 갱신과 재연결REST 응답과 소켓 이벤트 사이의 중복 처리이번 글에서는 Flutter와 Riverpod을 사용하는 앱에 socket_io_client 패키지로 실시간 채팅을 연결하면서 정한 역할 분리 방식과, 그 과정에서 실제로 겪은 메시지 중복 버그를 정리합니다.1. REST와 소켓의 역할 나누기채팅방 화면에 진입할 때는 크게 세 가지 작업이 필요합니다.GET /chats/rooms/:r..

Compose Multiplatform 프로젝트에 Glance 위젯을 추가했더니 Kotlin 컴파일러가 죽었다

안드로이드 홈 화면 위젯을 Jetpack Glance로 구현하다가 조금 특이한 문제를 만났다.코드 자체에는 특별한 문제가 없어 보였는데 Kotlin 컴파일러가 다음과 같은 내부 오류를 내면서 빌드가 실패했다.Back-end (JVM) Internal error:Couldn't inline method callCaused by: java.lang.IllegalStateException:couldn't find inline methodLandroidx/compose/runtime/CompositionLocal;.getCurrent()Ljava/lang/Object; 일반적인 unresolved reference나 타입 에러가 아니라 Kotlin JVM 백엔드가 바이트코드를 생성하는 과정에서 죽어버린 상황이었..

CMP 개발 기록 2026.08.09

Flutter 이미지 합성 기능 뜯어보기 (`image` 패키지)

들어가며앱 내에서 배너 이미지를 직접 등록할 수 있게 해뒀는데, 디자인을 할 줄 모르는 크리에이터 입장에서는 "배너"라는 것 자체가 진입장벽이었습니다.그래서 앱이 미리 만들어둔 배너 템플릿을 고르고, 자기 얼굴 사진만 원형으로 크롭해서 끼워 넣으면 완성된 배너 하나가 만들어지도록 구현해보기로 했습니다. 템플릿은 디자인은 완성돼 있고, 얼굴 사진이 들어갈 자리만 동그랗게 비어 있는 이미지입니다. 이 글에서는 이 기능의 핵심인 GachaBannerTemplateComposer 클래스가 내부적으로 어떻게 동작하는지, 그리고 이 과정에서 실제로 겪었던 버그 하나를 어떻게 고쳤는지를 코드와 함께 정리합니다.1. 요구사항을 다시 정리하면앱은 미리 준비된 배너 템플릿 이미지들을 여러 장 갖고 있다.assets/ima..

화면마다 따로 놀던 좋아요, 댓글 수 — Flutter Riverpod 피드 상태 정규화

앱에서 하나의 피드는 여러 화면에 등장한다.홈 피드내 프로필다른 사용자의 공개 프로필피드 상세처음에는 각 화면이 서버에서 조회한 FeedItem 목록을 자신의 상태에 보관했다. 홈은 홈대로 피드 목록을 가지고 있었고, 프로필과 피드 상세 화면도 각각 별도의 사본을 가지고 있었다.조회만 할 때는 별문제가 없었다. 하지만 좋아요, 잠금 해제, 댓글 작성처럼 피드의 상태가 변경되기 시작하면서 문제가 생겼다.같은 피드인데 화면마다 상태가 달랐다예를 들어 홈에서 피드 상세 화면으로 이동한 뒤 좋아요를 누른다고 해보자.피드 상세 화면에서는 좋아요 수와 좋아요 여부가 바로 변경된다. 하지만 뒤로 돌아가 홈 화면을 확인하면 홈은 변경 전 상태를 그대로 보여준다.두 화면이 동일한 피드를 보여주고 있지만, 실제로는 서로 ..

Flutter Dio 401 토큰 갱신 인터셉터 구현: 동시 요청부터 데드락까지

들어가며앱을 사용하다가 액세스 토큰이 만료되면 보통 두 가지 중 하나가 발생한다.갑자기 로그아웃되거나, 앱이 조용히 새 토큰을 받아와 아무 일 없었던 것처럼 원래 요청을 이어가거나. 나는 후자를 만들기로 결정했다. 이미 요청 헤더에는 토큰을 붙이는 AuthInterceptor가 있었기 때문에 여기에 401 처리 로직만 추가하면 될 것 같았다.하지만 실제로 구현해보니 동시 요청, 인터셉터 재진입, 데드락 같은 문제가 생길 수 있었다. 이번 글에서는 왜 토큰 갱신 로직을 별도 인터셉터로 분리했고, QueuedInterceptor와 별도의 Dio 인스턴스를 사용했는지 정리한다.왜 AuthInterceptor에 합치지 않았나기존 AuthInterceptor는 요청 전에 저장된 액세스 토큰을 읽고 Authoriz..

기존 Android·iOS 앱에 Compose Multiplatform 적용하기 — iOS에 shared 모듈 연결

기존에 Android와 iOS가 각각 네이티브로 구현된 "일단" 앱을 Compose Multiplatform(CMP) 구조로 점진적으로 전환하기로 했다.기존 프로젝트의 플랫폼별 기술 스택은 다음과 같았다.Android: Kotlin + Jetpack ComposeiOS: Swift + SwiftUI두 플랫폼을 별도로 운영하다 보니 동일한 기능을 Android와 iOS에 각각 구현해야 했다. 정책이나 UI가 변경될 때도 양쪽 코드를 따로 수정해야 했기 때문에 유지보수 비용이 계속 커졌다.프로젝트 팀원들이 모두 취업하면서 프로젝트를 중단하는 방향으로 이야기가 됐지만, 개인적으로라도 앱을 계속 유지해 보기로 결정했다.앱을 유지하기로 한 이유는 다음과 같다.소수지만 실제로 앱을 사용하는 사용자가 있다.별도로 홍..

CMP 개발 기록 2026.07.13