GA4 사용자 획득 보고서를 보다가 이상한 현상을 발견했습니다. 안드로이드 신규 사용자의 유입 소스가 google-play와 (direct)로 거의 반반씩 나뉘어 있었던 것입니다.Google Play 스토어만 통해 배포되는 앱인데 신규 사용자의 절반이 (direct)라면, 어트리뷰션 파이프라인 어딘가에서 소스 정보가 유실되고 있다는 뜻입니다. 처음에는 SDK나 어트리뷰션 툴 문제를 의심했지만, 결론부터 말하면 범인은 둘 다 아니었습니다. 원인을 찾아가는 과정과 체크리스트를 공유합니다.🛠️ 분석 환경플랫폼: Android (Google Play 단독 배포)어트리뷰션: AppsFlyer OneLink (UTM 포함)분석 도구: Firebase Analytics, BigQuery, GA4🔍 가설 검증 과정..
지도에 마커를 띄우는 것까지는 쉽다. 까다로워지는 건 그 마커가 선택될 수 있고, 줌 레벨에 따라 클러스터로 묶였다 풀렸다 하는 순간부터다. IamGround의 코트 지도에서 "선택된 마커를 강조한다"는 요구사항을 구현하면서 정리한, 핵심만 추린 기록이다.목표는 한 줄로 요약된다.selectedMarkerIds가 바뀌면, 화면의 leaf 마커와 클러스터 마커의 아이콘을 그에 맞게 갱신한다.이걸 제대로 하려면 네 가지를 풀어야 했다. 변경 감지, 변경분만 갱신, 클러스터 tag 처리, 그리고 아이콘 캐싱.1. 변경된 마커만 골라낸다가장 단순한 구현은 selectedMarkerIds가 바뀔 때마다 화면의 모든 마커를 순회하며 아이콘을 다시 만드는 것이다. 하지만 selected가 하나 토글됐다고 전체를 재생..
배경 — 왜 공유가 필요한가검색 리스트 화면과 지도 화면처럼, 서로 다른 destination이지만 같은 상태(검색 조건·결과)를 유지해야 하는 경우가 있습니다.각 화면이 별도 ViewModel을 갖는다면 상태 동기화가 복잡해집니다. 이때 두 가지 방법을 고려할 수 있습니다.SharedViewModelStoreNavEntryDecorator 방식기존 back stack 안에서 특정 화면들이 느슨하게 ViewModelStore를 공유합니다.① NavDisplay에 decorator 등록NavDisplay( backStack = navigator.state.backStack, onBack = { navigator.goBack() }, entryDecorators = listOf( ..
[Android] 홈 화면에서 Massive ViewModel 문제 리팩토링기능이 늘어날수록 ViewModel도 함께 늘어난다. 어느 순간 파일을 열면 UseCase가 열 개, when 분기가 스무 개. 클래스 하나가 너무 많은 걸 알고 있을 때, 어떻게 정리할 수 있을까?징후 - God Object가 된angangmoddi.tistory.com위 글에서 이어짐 징후 - HomeState가 Delegator 데이터까지 들고 있다이전 글의 최종 구조를 다시 보자.class HomeViewModel @Inject constructor( private val marketingDelegator: MarketingDelegator, private val scheduleDelegator: Schedule..
문제 발생앱에서 WebView를 사용할 때 흔히 겪는 현상이 있다. 첫 번째 진입 시에는 렌더링이 눈에 띄게 느린데, 한 번 들어갔다 나온 뒤 다시 진입하면 훨씬 빠르게 표시된다. 데이터나 네트워크 문제가 아닌데 왜 첫 번째만 느린지 원인을 찾아봤다.원인 — Chromium 렌더러 프로세스Android WebView는 내부적으로 Chromium 엔진을 사용한다. 앱 실행 시 이 렌더러 프로세스는 초기화되지 않은 상태다. `WebView(context)`로 객체를 생성하는 것만으로는 렌더러 프로세스가 시작되지 않고, 실제 페이지 로드가 시작되어야 초기화된다.첫 번째 WebView 진입 시 지연이 생기는 이유가 여기 있다. 렌더러 프로세스 시작, JavaScript 엔진 초기화, 실제 콘텐츠 로드가 한꺼번에..
문제 발생네트워크가 단절된 상태에서 API를 호출하면 에러가 발생한다.이때 사용자에게 "재시도 / 닫기" 다이얼로그를 보여주고, 재시도를 누르면 같은 요청을 다시 실행해야 한다. 단순하게 구현하면 각 호출부에서 재시도 로직을 직접 처리해야 한다.ViewModel마다 재시도 상태를 관리하고, 재시도 버튼 클릭 시 같은 함수를 다시 호출하는 코드가 반복된다.네트워크 단절이 발생할 수 있는 모든 곳에 이 처리가 필요하다. 목표는 호출부가 재시도 로직을 전혀 신경 쓰지 않아도 되는 구조를 만드는 것이다.전체 구조흐름ViewModel: runCatchingNetworking { api.getData() } ↓ 네트워크 단절suspendCancellableCoroutine — 코루틴 suspend ↓ErrorM..
시작은 클린 아키텍처였다프로젝트를 처음 구성할 때 클린 아키텍처를 도입했다. Presentation, Data, Domain 레이어로 나누고, ViewModel은 반드시 UseCase를 통해서만 데이터에 접근하도록 했다. 교과서적인 구조였고, 처음엔 나쁘지 않았다.문제는 "반드시 UseCase를 통해야 한다"는 규칙이 아무 로직도 없는 '단순 통과형' UseCase를 양산하기 시작하면서부터였다.class GetUserUseCase @Inject constructor( private val repository: UserRepository) { suspend operator fun invoke() = repository.getUser()}class GetData1UseCase @Inject con..
문제 발생Compose 기본 DropdownMenu는 드롭다운이 열릴 때 뒷배경에 아무것도 깔리지 않는다. 바깥을 터치하면 닫히긴 하지만, 배경이 투명해서 "열려 있다"는 시각적 피드백이 약하다. 디자인에서 Dialog처럼 반투명 검은 scrim을 깔아달라는 요청이 들어왔고, DropdownMenu는 scrim을 지원하지 않아서 직접 만들어야 했다.첫 시도 — Dialog를 scrim 레이어로scrim을 이미 깔아주는 컴포넌트가 있다. Dialog다. 그래서 이런 구조를 떠올렸다.Dialog ← scrim 담당 └─ Popup ← 실제 드롭다운 콘텐츠Dialog의 onDismissRequest는 비워두고 닫기는 Popup에서만 처리.Popup이 Dialog 위를 덮으니 터치는 Popup이 받는다. 여기..
- Total
- Today
- Yesterday
- 클래스
- 함수
- 연산자
- 형변환
- 모듈
- 백준
- 자료형
- 데이터베이스
- 문자열
- 파이썬
- navigation3
- 반복문
- 생성자
- 리스트
- DB
- if문
- 리사이클러뷰
- C언어
- compose
- 리스트뷰
- 코딩테스트
- 코틀린
- 조건문
- 자바
- 안드로이드
- 배열
- Android
- Kotlin
- recyclerview
- nodeJS
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 |